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
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"
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
The nil UUID (all zeros): "00000000-0000-0000-0000-000000000000"
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
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).