Generate ULIDs
Current time mode reads the clock once per click and gives each ID fresh randomness. IDs made in the same millisecond share a timestamp and their order is random. Monotonic mode keeps the order inside one millisecond by adding 1 to the random part, as the specification describes, and reports an error if the clock reads earlier than the last ID it made. Fixed time mode is for tests: every ID carries the time you pick.
Decode a ULID
The decoder reads the first 10 characters as the timestamp and the last 16 as randomness. Lowercase input is accepted and shown in its canonical uppercase form. The letters I and L are read as 1 and O as 0 when they appear, following Crockford's decoding rule, and the result says so. U is never valid in a ULID.
Check a list
Sorting ULIDs as text should give the same order as sorting them by time. This check reports the first line that breaks that order, so a list copied out of a log or a database dump can be checked at a glance.
How a ULID is built
A ULID is 128 bits, written as 26 characters. The timestamp comes first, which is what makes the whole string sort by time. The randomness comes second, so two IDs made in the same millisecond still differ.
crypto.getRandomValues.The alphabet is Crockford's Base32: digits and letters, minus I, L, O, and U. I and L look like 1, O looks like 0, and U is left out so random IDs do not spell words by accident. The same string reads the same in upper or lower case, and it needs no escaping in a URL.
Worked example
Take the timestamp 1712345678901 milliseconds and the random bytes 12, 240, 88, 3, 199, 45, 6, 231, 128, 17. Encoding the timestamp gives 01HTQW311N and encoding the random bytes gives 1KR5G0Y75M3EF00H. Joined, the ULID is 01HTQW311N1KR5G0Y75M3EF00H, which decodes to April 5, 2024 at 07:25:18 UTC. The timestamp part alone runs out in the year 10889, when the 48-bit counter reaches its maximum.
Monotonic generation, in one paragraph
When a monotonic generator is asked for another ID inside the same millisecond, it keeps the timestamp and adds 1 to the random part, carrying over as needed. That keeps the new ID after the previous one. If the random part is already all Zs, there is nothing left to add 1 to, and generation fails instead of repeating an ID. This page follows that rule in monotonic mode.
ULID vs UUID v7
UUID v7 is the other time-ordered ID most developers reach for. It puts the same 48-bit millisecond timestamp first, then version and variant bits, then 74 random bits, and it is defined by RFC 9562. ULID predates it and is defined by a community specification on GitHub, not by an RFC.
| Feature | ULID | UUID v7 |
|---|---|---|
| Length | 26 characters | 36 characters, with hyphens |
| Encoding | Crockford Base32, case-insensitive | Hex digits, lowercase by convention |
| Timestamp bits | 48 | 48 |
| Random bits | 80 | 74, plus version and variant bits |
| Standard | Community specification, no RFC | RFC 9562, May 2024 |
| Native database type | None; store as text, or convert the 128 bits at the boundary | Native uuid type in Postgres 18 and others |
| Monotonic mode | Described in the specification | Optional, left to the implementation |
Pick UUID v7 when the ID lives in a database column and the stack already speaks UUID. Pick ULID when the string itself is the surface: URLs, log lines, support tickets, and anywhere a shorter, case-tolerant ID is easier to read and copy. If you need the timestamp hidden, pick neither and use a random UUID v4 instead.
For a generator that makes UUID v7 IDs, see the UUID v7 generator on this site. It also explains the v7 bit layout.
When a ULID is the wrong choice
A ULID tells anyone who sees it exactly when it was made, to the millisecond. Two IDs minted close together also tell a reader roughly how busy the system was. Do not use ULIDs for rows where creation time is sensitive, and do not use them as secrets. A password reset link, a session token, or a capability URL needs a dedicated random token, not an ID whose first half is a clock reading.
ULIDs also depend on the clock. A machine whose clock jumps backwards can mint IDs that sort before IDs it already handed out, which breaks the ordering promise. Monotonic mode on this page refuses to do that and reports an error. In a real system, the same choice belongs in the generator you ship: wait, error, or accept the risk, and write down which one you picked.
Finally, ULID has no RFC behind it and thinner library support than UUID v7. Postgres 18 ships a native uuidv7() function and Python 3.14 ships uuid.uuid7(); ULID has no equivalent built into those platforms. Check that your stack parses ULIDs before you commit a schema to them. Storing the 26-character text also costs more per row than a 16-byte binary UUID column, so large tables usually convert the 128 bits at the application boundary instead.
Generate a ULID in code
Use a maintained library in production. The snippets below show the shape of the work so the format is easy to check by hand. The JavaScript version needs no library and matches what this page runs.
JavaScript, no library
const ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
function encodeTime(ms) {
let n = BigInt(ms), out = "";
for (let i = 0; i < 10; i++) { out = ALPHABET[Number(n % 32n)] + out; n /= 32n; }
return out;
}
function encodeRandom(bytes) {
let n = 0n;
for (const b of bytes) n = (n << 8n) | BigInt(b);
let out = "";
for (let i = 0; i < 16; i++) { out = ALPHABET[Number(n % 32n)] + out; n /= 32n; }
return out;
}
const bytes = new Uint8Array(10);
crypto.getRandomValues(bytes);
const ulid = encodeTime(Date.now()) + encodeRandom(bytes);
Python, standard library only
import secrets
ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
def encode_time(ms):
out = ""
for _ in range(10):
out = ALPHABET[ms % 32] + out
ms //= 32
return out
def encode_random(data):
n = int.from_bytes(data, "big")
out = ""
for _ in range(16):
out = ALPHABET[n % 32] + out
n //= 32
return out
ulid = encode_time(1712345678901) + encode_random(secrets.token_bytes(10))
Go, with the oklog library
import (
"math/rand"
"time"
"github.com/oklog/ulid"
)
entropy := rand.New(rand.NewSource(time.Now().UnixNano()))
id, err := ulid.New(ulid.Timestamp(time.Now()), entropy)
The Go library is the one listed in the ULID specification's implementations table. Check the version you install; library APIs change.
Frequently asked questions
How long is a ULID?
A ULID is always 26 characters: 10 characters encode a 48-bit millisecond timestamp, and 16 characters encode 80 bits of randomness. The characters come from Crockford's Base32 alphabet, which leaves out I, L, O, and U.
Is a ULID sortable by creation time?
Yes, when you compare the canonical uppercase form as plain text. The timestamp sits first, so sorting ULIDs as strings also sorts them by time. Within the same millisecond, order is random unless the IDs were made with a monotonic generator.
Can two ULIDs collide?
In practice, no. The 80 random bits give 1.21e24 distinct values for each millisecond. A collision becomes likely only after about 1.3 trillion IDs are generated inside one millisecond, which is the birthday bound for 80 bits.
Is a ULID a UUID?
No. A ULID and a UUID are both 128 bits, but they are different formats. A ULID is 26 Crockford Base32 characters under a community specification, while a UUID is 32 hex digits under RFC 9562. Writing the same 128 bits in UUID form does not make it a UUID version 7.
Can I use a ULID as a password reset token?
No. The first 10 characters of a ULID reveal when it was made, to the millisecond, and the remaining 80 bits are the only secret part. Use a dedicated random token for anything that grants access, and treat a ULID as a public identifier.
What happens if the clock goes backwards?
A monotonic generator must not emit an ID earlier than one it already emitted, so it either waits or reports an error. This page reports an error in monotonic mode when the clock reads earlier than the last ID it made, instead of emitting an out-of-order ID.
Sources
ULID project, Universally Unique Lexicographically Sortable Identifier: 48-bit millisecond timestamp, 80 bits of randomness, 10 plus 16 character canonical form, Crockford Base32 alphabet, monotonic increment, and the year 10889 limit.
Douglas Crockford, Base 32: the alphabet, why I, L, O, and U are excluded, and the decoding rule that reads I and L as 1 and O as 0.
IETF, RFC 9562: Universally Unique IDentifiers (UUIDs): the UUID v7 layout used in the comparison above, and the rule that UUIDs are not secrets.
Agents
Machine readable summary for AI assistants: