Skip to main content

UUID v7

UUID v7 is a time-ordered UUID that embeds a Unix timestamp in milliseconds, making IDs naturally sortable by creation time. This is ideal for database primary keys where chronological ordering improves index performance.

When to Use

Use UUID v7 when you need:
  • Database primary keys - Better index performance than random UUIDs
  • Time-ordered identifiers - IDs sort chronologically
  • Standard compliance - RFC 9562 (2024)
  • Monotonic sequencing - Multiple IDs per millisecond stay in order
UUID v7 is the recommended choice for database primary keys, combining the benefits of standard UUID format with time-ordered sorting.

Basic Usage

import { uuidv7 } from 'uniku/uuid/v7'

const id = uuidv7()
// => "018e5e5c-7c8a-7000-8000-000000000000"

// IDs are naturally sortable by creation time
const [first, second, third] = [uuidv7(), uuidv7(), uuidv7()]
console.log(first < second && second < third) // true

API Reference

Main Function

uuidv7()
() => string
Generate a UUID v7 string with current timestamp and automatic monotonic sequencing.
uuidv7(options)
(options?: UuidV7Options) => string
Generate a UUID v7 string with custom options.Options:
  • msecs?: number - Timestamp in milliseconds (defaults to Date.now())
  • seq?: number - 31-bit sequence number for monotonicity
  • random?: Uint8Array - 16 bytes of random data (several bytes will be overwritten with timestamp/version/variant)
uuidv7(options, buf, offset)
<TBuf extends Uint8Array>(options: UuidV7Options | undefined, buf: TBuf, offset?: number) => TBuf
Write UUID v7 bytes directly into a buffer at the specified offset.Parameters:
  • options - UUID generation options or undefined
  • buf - Target buffer (must have at least 16 bytes available from offset)
  • offset - Starting position in buffer (default: 0)
Returns: The same buffer passed in (for chaining)

Static Methods

uuidv7.toBytes(id)
(id: string) => Uint8Array
Convert a UUID v7 string to a 16-byte Uint8Array.
const bytes = uuidv7.toBytes("018e5e5c-7c8a-7000-8000-000000000000")
// => Uint8Array(16) [1, 142, 94, 92, 124, 138, 112, 0, ...]
uuidv7.fromBytes(bytes)
(bytes: Uint8Array) => string
Convert a 16-byte Uint8Array to a UUID v7 string.
const id = uuidv7.fromBytes(bytes)
// => "018e5e5c-7c8a-7000-8000-000000000000"
uuidv7.timestamp(id)
(id: string) => number
Extract the embedded timestamp from a UUID v7 string. Returns milliseconds since Unix epoch.
const id = uuidv7()
const ts = uuidv7.timestamp(id)
console.log(new Date(ts)) // Original creation time
uuidv7.isValid(id)
(id: unknown) => id is string
Validate that a value is a properly formatted UUID v7 string. TypeScript type guard.
uuidv7.isValid("018e5e5c-7c8a-7000-8000-000000000000") // true
uuidv7.isValid("not-a-uuid") // false

Constants

uuidv7.NIL
string
The nil UUID (all zeros): "00000000-0000-0000-0000-000000000000"
uuidv7.MAX
string
The max UUID (all ones): "ffffffff-ffff-ffff-ffff-ffffffffffff"

Monotonic Sequencing

UUID v7 maintains module-level state to ensure IDs generated within the same millisecond remain monotonically increasing:
import { uuidv7 } from 'uniku/uuid/v7'

// All generated in the same millisecond
const id1 = uuidv7()
const id2 = uuidv7()
const id3 = uuidv7()

// Still sortable due to sequence counter
console.log(id1 < id2 && id2 < id3) // true
State Persistence: The monotonic counter persists across all uuidv7() calls in the module’s lifetime. In serverless/edge functions with warm starts, this state persists between invocations.For isolated state in tests, provide explicit msecs and seq options.

Real-World Examples

Database Primary Keys

import { uuidv7 } from 'uniku/uuid/v7'

// Postgres with UUID column
await db.execute(`
  CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email TEXT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
  )
`)

// Insert with UUID v7 for better index performance
const userId = uuidv7()
await db.insert('users', {
  id: userId,
  email: '[email protected]'
})

// IDs naturally sort by creation time
const users = await db.query('SELECT * FROM users ORDER BY id')
// Returns users in chronological order!

Event Sourcing

import { uuidv7 } from 'uniku/uuid/v7'

interface Event {
  id: string
  type: string
  timestamp: number
  data: unknown
}

function createEvent(type: string, data: unknown): Event {
  const id = uuidv7()
  return {
    id,
    type,
    timestamp: uuidv7.timestamp(id), // Extract embedded timestamp
    data
  }
}

// Events are sortable by ID alone
const events = [
  createEvent('user.created', { email: '[email protected]' }),
  createEvent('user.updated', { email: '[email protected]' }),
  createEvent('user.deleted', { userId: '123' })
]

events.sort((a, b) => a.id.localeCompare(b.id))

Distributed ID Generation

import { uuidv7 } from 'uniku/uuid/v7'

// Multiple servers can generate IDs without coordination
// IDs from all servers will still be globally sortable by time

// Server A
const idFromA = uuidv7()

// Server B (at approximately the same time)
const idFromB = uuidv7()

// IDs maintain rough chronological order across servers
// (accurate to millisecond precision)

Testing with Deterministic Output

import { uuidv7 } from 'uniku/uuid/v7'

// Provide explicit timestamp and sequence for reproducible tests
const testId = uuidv7({
  msecs: 1702387456789,
  seq: 0,
  random: new Uint8Array(16).fill(0)
})
// => "018c5f1a-4e15-7000-8000-000000000000"

// Same inputs = same output
const testId2 = uuidv7({
  msecs: 1702387456789,
  seq: 0,
  random: new Uint8Array(16).fill(0)
})
console.log(testId === testId2) // true

Type Definitions

type UuidV7Options = {
  /**
   * 16 bytes of random data to use for UUID generation.
   * Note: Several bytes will be overwritten with timestamp, 
   * version, and variant data.
   */
  random?: Uint8Array
  /** Timestamp in milliseconds since Unix epoch */
  msecs?: number
  /** 31-bit sequence number for monotonic ordering */
  seq?: number
}

type UuidV7 = {
  (): string
  <TBuf extends Uint8Array = Uint8Array>(
    options: UuidV7Options | undefined, 
    buf: TBuf, 
    offset?: number
  ): TBuf
  (options?: UuidV7Options, buf?: undefined, offset?: number): string
  toBytes(id: string): Uint8Array
  fromBytes(bytes: Uint8Array): string
  timestamp(id: string): number
  isValid(id: unknown): id is string
  NIL: string
  MAX: string
}

Structure

UUID v7 consists of 128 bits:
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          unix_ts_ms                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       seq_hi          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|               seq_lo               |       random          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            random                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • Bytes 0-5: 48-bit timestamp (milliseconds since Unix epoch)
  • Byte 6: Version (0x7) + high 4 bits of sequence
  • Bytes 7-10: Remaining sequence bits (31 bits total) + random bits
  • Bytes 11-15: Random data

Performance Characteristics

Generation Speed

Very fast - optimized hot path for default usage

Sortability

Naturally sortable by creation time (millisecond precision)

Index Performance

Better than random UUIDs - reduces B-tree splits

Size

36 characters (string) or 16 bytes (binary)
Bundle Size: ~1.1 KB minified + gzipped

Validation Pattern

UUID v7 must match this pattern:
/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
Key characteristics:
  • 36 characters total (32 hex + 4 hyphens)
  • Version nibble (13th character) is always 7
  • Variant bits (17th character) is always 8, 9, a, or b

Migration Guide

From uuid Package

- import { v7 as uuidv7 } from 'uuid'
+ import { uuidv7 } from 'uniku/uuid/v7'

  const id = uuidv7()
API is identical - drop-in replacement.

From UUID v4 or Random IDs

- import { uuidv4 } from 'uniku/uuid/v4'
+ import { uuidv7 } from 'uniku/uuid/v7'

  const id = uuidv7()
Benefit: Better database index performance with no application changes needed.
For even shorter time-ordered IDs, consider ULID which uses Crockford Base32 encoding (26 characters vs 36).

Build docs developers (and LLMs) love