Create ULID identifiers instantly with a simple online ULID generator—ideal for sortable unique IDs in apps, databases, and logs.
ULID = 10 chars timestamp (48-bit) + 16 chars randomness (80-bit). Lexicographically sortable. Crockford base32 (no I, L, O, U).
ULIDs (Universally Unique Lexicographically Sortable Identifiers) solve a problem that plain UUID v4 cannot: they are unique like a UUID, but they also sort by creation time. That makes them ideal for database primary keys, event-stream keys, and any system where you want chronological ordering without giving up the coordination-free generation of UUIDs. XConvert's free ULID Generator creates spec-compliant 26-character ULIDs entirely in your browser — no server roundtrip, no logging, no sign-up.
.txt file with one ULID per line for pasting into SQL INSERT statements, seed scripts, or test fixtures.A ULID is a 128-bit identifier specified by ulid/spec on GitHub, the canonical reference maintained by Alizain Feerasta. Its layout is intentionally minimal: 48 bits of millisecond-precision Unix timestamp followed by 80 bits of randomness, encoded as a 26-character string using Crockford's Base32 alphabet. The alphabet 0123456789ABCDEFGHJKMNPQRSTVWXYZ deliberately excludes I, L, O, and U to avoid visual ambiguity and accidental profanity.
Because the timestamp lives in the high-order bits, ULIDs sort the same way as their creation order under any byte-wise or string comparison. That property is what makes them useful in B-tree-indexed databases: inserts cluster at the end of the index instead of scattering randomly across the page tree like UUID v4 inserts do, which reduces fragmentation and improves cache locality for time-range queries. The 80 bits of randomness give roughly 1.2 × 10^24 possible values per millisecond, so collisions within a single millisecond on a single node are statistically negligible.
ULIDs are fully compatible with UUID storage. Both are 128 bits, so a ULID drops directly into a PostgreSQL uuid column, a MySQL BINARY(16) column, or any other 16-byte field — you only need to convert between the 26-character Crockford Base32 representation and the 16-byte binary at the application layer. The maximum representable timestamp is 2^48 - 1 milliseconds, which is valid through the year 10889 AD.
| Property | ULID | UUID v7 | KSUID | Snowflake |
|---|---|---|---|---|
| Total bits | 128 | 128 | 160 | 64 |
| String length | 26 chars | 36 chars | 27 chars | up to 19 digits |
| Encoding | Crockford Base32 | Hex with hyphens | Base62 | Decimal |
| Timestamp bits | 48 (ms) | 48 (ms) | 32 (sec) | 41 (ms) |
| Random/sequence bits | 80 | ~74 (after version/variant) | 128 | 22 (10 worker + 12 seq) |
| Lexicographically sortable | Yes | Yes | Yes | Yes (numeric) |
| Fits UUID column (128-bit) | Yes | Yes | No (needs CHAR(27)/BINARY(20)) | No (fits BIGINT) |
| Standardized by | github.com/ulid/spec | RFC 9562 (2024) | segmentio/ksuid | Twitter (2010) |
| Coordination-free | Yes | Yes | Yes | No (needs worker ID) |
| Specified monotonic mode | Yes (counter+carry) | Optional (RFC 9562) | No | Yes (per-worker seq) |
| Index | Char | Index | Char | Index | Char | Index | Char |
|---|---|---|---|---|---|---|---|
| 0 | 0 | 8 | 8 | 16 | G | 24 | R |
| 1 | 1 | 9 | 9 | 17 | H | 25 | S |
| 2 | 2 | 10 | A | 18 | J | 26 | T |
| 3 | 3 | 11 | B | 19 | K | 27 | V |
| 4 | 4 | 12 | C | 20 | M | 28 | W |
| 5 | 5 | 13 | D | 21 | N | 29 | X |
| 6 | 6 | 14 | E | 22 | P | 30 | Y |
| 7 | 7 | 15 | F | 23 | Q | 31 | Z |
The four excluded letters — I, L, O, U — are skipped because they look like digits or each other in many fonts. Decoders typically also treat lowercase as equivalent to uppercase and accept I/i → 1, L/l → 1, O/o → 0 for forgiving input parsing.
Time-ordered database primary keys — Use ULIDs as primary keys in tables with high write volume to avoid the B-tree index fragmentation that random UUID v4 keys cause. Because new inserts cluster at the end of the index, page splits drop and cache hit rates rise. Works directly with the PostgreSQL uuid type, MySQL BINARY(16), and SQL Server UNIQUEIDENTIFIER.
Event sourcing and audit logs — Assign a ULID to each event in an event store, message queue (Kafka, Kinesis, SQS), or audit log. The embedded millisecond timestamp gives you a natural ordering key, so you can replay events in creation order or query "everything between two timestamps" by string range alone — no separate created_at index required.
Distributed tracing and correlation IDs — Use ULIDs as request IDs across microservices. The timestamp prefix lets ops engineers eyeball the age of an ID in a log line ("starts with 01HD..., that's recent") without parsing it, and the lexicographic sort makes log aggregators sort trace events correctly even when clocks drift slightly between nodes.
Cursor-based pagination — When you need a stable, sort-friendly cursor for paginating API results, ULIDs work better than offset pagination (which breaks under concurrent inserts) and better than created_at + id composite cursors (which need two columns). Pass the last seen ULID as the ?after= cursor; the next page is WHERE id > :cursor ORDER BY id LIMIT n.
Test fixtures and seed data — Generate batches of ULIDs for integration tests, load-test scripts, or development database seeds. Unlike sequential integers, ULIDs are unique across runs, so reseeding a dev database does not collide with leftover state from a previous run.
Idempotency keys and dedup tokens — Assign a ULID at request creation time and use it as the idempotency key for payment APIs, webhook delivery, or job queue dedup. The 80 bits of randomness make replay attacks impractical, and the timestamp prefix gives you a free TTL signal — drop the key after N days by comparing the embedded timestamp.
XConvert's ULID Generator follows the ulid/spec reference layout exactly: 48 bits of Unix time in milliseconds (MSB-first), then 80 bits of randomness sourced from the browser's crypto.getRandomValues() (the Web Crypto CSPRNG), then the 16-byte big-endian payload encoded to 26 characters using the Crockford Base32 alphabet. The first character is always one of 0 through 7 because the 48-bit timestamp only needs 48 of the 50 bits that 10 Base32 characters can represent — the top 2 bits of the leading character are always zero today, and they stay zero until the year 10889 AD when the timestamp would exceed 2^48 - 1.
Monotonic mode follows the spec algorithm precisely. The generator remembers the last (timestamp, random) pair it produced; if the next generation happens within the same millisecond, the 80-bit random tail is incremented by one (with carry into the upper bytes) instead of being regenerated. If the increment would overflow 2^80 - 1 within a single millisecond — which would require generating more than 1.2 × 10^24 ULIDs in 1 ms — the spec mandates that the generator raise an error rather than silently roll over. In practice, no real workload approaches this limit.
All randomness is sourced from the Web Crypto API's crypto.getRandomValues(), which is backed by the operating system CSPRNG (/dev/urandom on Linux/macOS, BCryptGenRandom on Windows, getentropy on iOS). Bulk generation issues a single getRandomValues() call sized to cover every ULID in the batch, then slices the buffer in 10-byte chunks per ULID — measurably faster than calling the API once per ULID. Generation, encoding, and rendering all happen client-side; your ULIDs are never sent to any server, which matters when they are used as session tokens, idempotency keys, or other secrets.
Default to ULID over UUID v4 for new tables — If you are designing a new schema, ULID gives you all the coordination-free benefits of UUID v4 plus time-ordering for free. The only reason to stick with UUID v4 is if you already have ecosystem tooling (ORMs, validators) that assumes the RFC 4122 version/variant bits — and even then, UUID v7 is now the better choice on the UUID side.
Store as 16-byte binary, not as the 26-char string — Storing ULIDs as BINARY(16) or PostgreSQL uuid saves ~60% versus storing the 26-char Crockford representation. The Crockford string is for humans; the binary is for the index. Convert at the application layer.
Use monotonic mode when generating many IDs in a tight loop — If you are emitting hundreds of events per millisecond from a single process, enable monotonic mode so the order in which you emitted them is preserved in the index. Without it, two ULIDs from the same millisecond sort by their independent random tails, which is effectively random.
Do not parse the timestamp for security decisions — The embedded timestamp is in clear text and can be read by anyone with the ULID. Treat the timestamp as observable information; if you need creation time to be private, do not use ULIDs (or UUID v7, or KSUIDs) for the public-facing identifier.
Watch out for clock skew across nodes — ULIDs sort by the timestamp the generator's clock saw. If two nodes have clocks 50 ms apart, their ULIDs interleave incorrectly in the global sort order by up to 50 ms. NTP keeps most fleets within a few ms; for stricter ordering, generate ULIDs centrally or use a Snowflake-style scheme with a coordinated epoch.
Pair with /convert-timestamp-to-date to decode the prefix — The first 10 characters of a ULID are the Crockford Base32 encoding of the Unix timestamp in milliseconds. Decode them back to a Unix millisecond value, then pass that value through XConvert's timestamp-to-date converter to get a human-readable date. For different ID needs see XConvert's UUID v4 Generator.
They are nearly identical in design: both are 128-bit, both embed a 48-bit Unix millisecond timestamp in the high bits, both are lexicographically sortable. The differences are: (1) ULID encodes to 26 Crockford Base32 characters while UUID v7 encodes to the 36-character 8-4-4-4-12 hex format defined by RFC 4122 / RFC 9562; (2) UUID v7 spends 4 bits on the version marker and 2 bits on the variant bits, leaving roughly 74 bits of randomness versus ULID's full 80; (3) UUID v7 is now an IETF standard (RFC 9562, May 2024), while ULID's canonical spec lives at github.com/ulid/spec. Pick UUID v7 if you need ecosystem compatibility with UUID tooling and validators. Pick ULID if you want a more compact string or are already standardized on Crockford Base32 elsewhere.
Strictly sortable to millisecond precision. Two ULIDs generated in different milliseconds sort by their timestamps under any byte-wise, string, or memcmp comparison — this is true whether you sort the 26-character Crockford string, the 16-byte binary representation, or the value stored in a uuid column. Two ULIDs generated in the same millisecond sort by their 80-bit random tails, which is effectively random — unless you use monotonic mode, in which case the second and subsequent ULIDs in that millisecond are guaranteed to sort after the first.
A 128-bit value encoded in Base32 (5 bits per character) needs ceil(128/5) = 26 characters. The 26th character carries only 3 information bits, and the leading character carries only 6 information bits — the other 2 bits of the leading character are always zero today because the 48-bit timestamp's high bits are unused until the year 10889 AD. That is why every ULID generated today starts with a character in the range 0–7 (Crockford Base32 indices 0–7).
One millisecond. The 48-bit timestamp is the number of milliseconds since the Unix epoch (1970-01-01T00:00:00Z), giving roughly 281 trillion distinct values before rolling over in the year 10889 AD. Sub-millisecond ordering is not part of the spec — that is what the random component (or monotonic mode) is for.
In the default (non-monotonic) mode, each ULID gets a fresh 80-bit random tail, so they have identical 10-character timestamp prefixes but different 16-character random suffixes. Their sort order within that millisecond is effectively random. In monotonic mode, the spec mandates that the second ULID's random tail is the first one's tail plus 1 (with carry), so they sort in generation order. Monotonic mode fails (raises an error) only if you try to generate more than 2^80 ULIDs within a single millisecond — a limit no real workload reaches.
Yes. Both are 128 bits, so a ULID drops straight into a PostgreSQL uuid column or a MySQL BINARY(16) column at the binary level. You only need an application-layer codec to convert between the binary 16 bytes and the human-readable 26-character Crockford string. Most ULID libraries (ulid on npm, python-ulid on PyPI, oklog/ulid in Go) ship with helpers for this conversion. Storing the 26-character string in a VARCHAR(26) works but wastes about 60% of the disk space versus binary storage.
No. Generation happens entirely in your browser using crypto.getRandomValues(). The ULIDs never leave the page, are not logged, and are not transmitted to XConvert or any third party. This matters because ULIDs are often used as idempotency keys, session tokens, or other values that must remain confidential.
Yes. Take the first 10 characters, decode them as a Crockford Base32 number (5 bits each, 50 bits total — the top 2 bits are always zero), and you have the Unix millisecond timestamp the ULID was generated at. Most ULID libraries expose this as ulid.decodeTime(id) or similar. You can then feed that millisecond value into XConvert's timestamp-to-date converter for a human-readable date.
Yes. The canonical specification at github.com/ulid/spec has been stable since 2017 and has not changed in any binary-breaking way. The 1.0 layout — 48-bit timestamp, 80-bit randomness, 26-character Crockford Base32 — is what every mainstream implementation uses. UUID v7 (RFC 9562) was published in May 2024 and is a different identifier; it does not supersede ULID.