正在加载,请稍候…

How to Generate ULIDs: Format, Sorting, and Real-World Usage

Generate ULIDs and learn the 26-character format, how lexicographic sorting works, monotonic generation, and when ULIDs beat UUIDs as database keys.

What a ULID Actually Is

A ULID (Universally Unique Lexicographically Sortable Identifier) is a 128-bit identifier written as 26 characters, designed to be unique like a UUID but sortable by creation time. A typical ULID looks like this:

01ARZ3NDEKTSV4RRFFQ69G5FAV

The 128 bits are split into two parts: the first 48 bits are a millisecond Unix timestamp, and the remaining 80 bits are random. Because the timestamp comes first and the encoding preserves byte order, sorting ULIDs as plain strings sorts them by time — which is the whole point.

The 26-Character Format

ULIDs are encoded with Crockford's Base32 (digits 0-9 and letters A-Z, excluding I, L, O, and U to avoid ambiguity). The layout is fixed:

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

A few consequences fall out of this:

  • ULIDs are case-insensitive and contain no special characters, so they are safe in URLs without encoding.
  • The timestamp covers dates until the year 10889, so overflow is not a practical concern.
  • 80 bits of randomness means collisions within the same millisecond are astronomically unlikely.

Generating a ULID in Code

Most languages have a small library. In JavaScript:

import { ulid } from 'ulid';

ulid(); // '01ARZ3NDEKTSV4RRFFQ69G5FAV'

// You can also pass an explicit timestamp (ms)
ulid(1469918176385); // '01ARYZ6S41TSV4RRFFQ69G5FAV'

In Python:

from ulid import ULID

str(ULID())  # '01ARZ3NDEKTSV4RRFFQ69G5FAV'

Decoding the timestamp back out is straightforward, because the first 10 characters are just the Base32-encoded milliseconds:

import { decodeTime } from 'ulid';

decodeTime('01ARZ3NDEKTSV4RRFFQ69G5FAV'); // 1469918176385

Monotonic Generation

Within a single millisecond, two plain ULIDs are randomly ordered relative to each other. If you generate many IDs per millisecond and need them strictly increasing, use a monotonic factory, which increments the random component instead of regenerating it:

import { monotonicFactory } from 'ulid';
const next = monotonicFactory();

next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAV
next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAW  ← +1, same ms
next(); // 01ARZ3NDEKTSV4RRFFQ69G5FAX

This matters when ULIDs are used as primary keys and you rely on them to reflect insertion order.

ULID vs UUID as a Database Key

The practical reason teams reach for ULIDs is index performance. Random UUIDv4 keys scatter inserts across a B-tree index, causing page splits and poor cache locality. Because ULIDs are time-ordered, new rows append near the "right edge" of the index, much like an auto-increment integer — but without a central sequence and without leaking a row count.

Property UUIDv4 ULID
Sortable by time No Yes
Index locality on insert Poor (random) Good (sequential)
Length as text 36 chars 26 chars
Reveals creation time No Yes (timestamp embedded)
URL-safe without encoding No (hyphens) Yes

The one trade-off worth noting: a ULID exposes its creation time to anyone who has it. If a public-facing identifier must not reveal when a record was created, use a random UUID instead.

When to Use ULIDs

ULIDs are a strong default for:

  • Primary keys in high-insert tables (orders, events, messages)
  • Distributed systems that need client-generated IDs without coordination
  • Log and event identifiers where time-ordering aids debugging
  • Public IDs where a compact, URL-safe string is preferable to a UUID

Avoid them when creation time must stay private, or when an existing system mandates UUID format.

Frequently Asked Questions

How long is a ULID? 128 bits, written as 26 characters in Crockford Base32 — 10 characters of timestamp followed by 16 characters of randomness.

Are ULIDs guaranteed unique? Not mathematically guaranteed, but with 80 random bits per millisecond a collision is so improbable it can be treated as unique in practice. Use monotonic generation to also guarantee ordering within a millisecond.

Can I get the timestamp back from a ULID? Yes. The first 10 characters decode to the millisecond Unix timestamp, so no separate created_at column is strictly required.

Are ULIDs better than UUIDs? For time-ordered, index-friendly keys, yes. For identifiers that must not leak creation time, a random UUID is better. They solve different problems.

Use the ULID generator above to create single or bulk ULIDs, inspect the embedded timestamp, and copy them straight into your database or test fixtures.