Reference Guide

ULID: The Sortable Alternative to UUID

Everything you need to know about ULIDs — Universally Unique Lexicographically Sortable Identifiers. How they work, when to use them, and how they compare to UUIDs.

What Is a ULID?

A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier designed as a drop-in replacement for UUID that adds lexicographic sortability. Created by Alizain Feerasta, ULIDs solve the primary weakness of UUID v4: the inability to sort by creation time.

A ULID looks like this:

01ARZ3NDEKTSV4RRFFQ69G5FAV

It’s 26 characters long, encoded in Crockford’s Base32, and encodes both a timestamp and randomness in a single, compact string.


Structure

A ULID consists of two components packed into 128 bits:

 01ARZ3NDEK        TSV4RRFFQ69G5FAV
|----------|      |----------------|
 Timestamp              Randomness
  48 bits                 80 bits
  10 chars                16 chars

Timestamp (48 bits / 10 characters)

The first 48 bits encode a Unix timestamp in milliseconds. This gives:

  • Resolution: 1 millisecond
  • Range: Until the year 10889 (more than enough)
  • Sorting: Because the timestamp is the most significant bits, ULIDs naturally sort by creation time

Randomness (80 bits / 16 characters)

The remaining 80 bits are filled with cryptographically secure random data. This provides:

  • 2^80 possible values per millisecond (~1.2 × 10^24)
  • Collision probability within the same millisecond is astronomically low

Crockford’s Base32 Encoding

ULIDs use Crockford’s Base32 encoding, which has several advantages over standard hex or Base64:

Alphabet: 0123456789ABCDEFGHJKMNPQRSTVWXYZ

Key design decisions:

  • No I, L, O, U — avoids confusion with 1, l, 0, and offensive words
  • Case-insensitive01ARZ3NDEK = 01arz3ndek
  • URL-safe — no special characters
  • Human-readable — designed to be easy to read, type, and communicate verbally

Monotonic Sort Order

When multiple ULIDs are generated within the same millisecond, the random component is incremented rather than regenerated. This guarantees strict monotonic ordering even within the same timestamp:

01ARZ3NDEKTSV4RRFFQ69G5FAV  ← t=1000ms, random=X
01ARZ3NDEKTSV4RRFFQ69G5FAW  ← t=1000ms, random=X+1
01ARZ3NDEKTSV4RRFFQ69G5FAX  ← t=1000ms, random=X+2

This is particularly important for database operations where insert order matters.


ULID vs UUID Comparison

FeatureULIDUUID v4UUID v7
Length (text)26 chars36 chars36 chars
EncodingCrockford Base32Hex + hyphensHex + hyphens
Sortable by timeYesNoYes
Timestamp embeddedYes (48-bit ms)NoYes (48-bit ms)
Random bits8012274
Possible values2^80 per ms (~1.2 × 10^24)2^122 total (~5.3 × 10^36)2^74 per ms (~1.9 × 10^22)
Case-sensitiveNoNoNo
URL-safeYesYes (with hyphens)Yes (with hyphens)
Human-readableBetterGoodGood
StandardCommunity specRFC 9562RFC 9562

When to Choose ULID Over UUID

  • Compact representation — 26 chars vs 36
  • Better readability — no hyphens, no ambiguous characters
  • Monotonic guarantee — strict ordering within same millisecond
  • URL-friendliness — no special characters at all

When to Choose UUID Over ULID

  • RFC standardization — UUIDs are an IETF standard (RFC 9562)
  • Broader ecosystem support — native UUID types in most databases
  • Multiple versions — deterministic (v3/v5), timestamp (v1/v6/v7), custom (v8)
  • More random bits — v4 has 122 random bits vs ULID’s 80

Database Considerations

As Primary Keys

ULIDs make excellent primary keys because:

  1. Sequential inserts — monotonic ordering means new rows are always appended, not inserted randomly into the B-tree
  2. No hotspots — unlike auto-increment, ULIDs don’t create contention on a single counter
  3. Distributed generation — no coordination needed between nodes
  4. Embedded timestamp — free “created_at” metadata

Storage

Store ULIDs as:

  • Binary(16) — most efficient (128 bits = 16 bytes)
  • CHAR(26) — human-readable, slightly larger
  • UUID type — convert ULID to UUID binary format for databases with native UUID support

Extracting the Timestamp

You can extract the creation timestamp from any ULID:

1
2
3
4
5
6
7
// Decode first 10 characters (48-bit timestamp)
const chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
let timestamp = 0;
for (const c of ulid.slice(0, 10)) {
  timestamp = timestamp * 32 + chars.indexOf(c);
}
const date = new Date(timestamp);

What Is a CSPRNG?

A CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) is a random number generator that produces output suitable for use in cryptography — meaning the output is statistically indistinguishable from true randomness and computationally infeasible to predict, even if an attacker knows previous outputs.

Regular random number generators (like JavaScript’s Math.random()) use simple algorithms that are fast but predictable. If an attacker observes enough outputs, they can reverse-engineer the internal state and predict future values. This is unacceptable for generating unique identifiers, passwords, or encryption keys.

A CSPRNG solves this by drawing entropy from unpredictable physical sources — hardware interrupts, CPU timing jitter, mouse movements, and other environmental noise — then mixing it through cryptographic algorithms to produce uniformly distributed, unpredictable output.

In browsers, the CSPRNG is available through the Web Crypto API:

1
2
3
// Generate 16 random bytes using the browser's CSPRNG
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);

All Safe Pass Guru generators use crypto.getRandomValues() exclusively — Math.random() is never used.


Security Considerations

  • Like UUIDs, ULIDs are not secrets — don’t use them as authentication tokens
  • The embedded timestamp reveals when the ID was created — consider whether this is acceptable for your use case
  • The monotonic increment within the same millisecond makes successive ULIDs predictable within that window
  • Always use a CSPRNG for the random component — our tool uses crypto.getRandomValues()

When to Use ULIDs

Use ULIDs when you need:

  • Compact, sortable, unique identifiers
  • No external dependencies or coordination
  • Clean URLs and human-friendly IDs
  • Database-friendly sequential inserts

Consider alternatives when:

  • You need RFC-standardized identifiers (use UUID v7)
  • You need deterministic IDs from names (use UUID v5)
  • You need maximum random bits (use UUID v4)
  • Your database has native UUID support with optimized indexing