Skip to content

ULID Generator

Generate lexicographically sortable ULIDs with real monotonic ordering, read the timestamp back out of one, and convert between ULID and UUID.

ULID

Generating…

Generated locally with your browser's cryptographic random number generator. Values are never sent to a server, logged, or reused.

About the ULID Generator

This ULID generator creates lexicographically sortable identifiers in your browser: 26 characters of Crockford base32, holding a 48-bit millisecond timestamp followed by 80 random bits. Because the timestamp leads and base32 preserves ordering, sorting the strings sorts by creation time — no separate created_at column, no index on it, and no scattered writes.

Generation is monotonic by default, which is the part most ULID generators skip. Two identifiers created in the same millisecond have independent random components, so their relative order is a coin toss. The specification's answer is to increment the previous random value by one instead of drawing a new one, and that is what happens here — generate 500 at once and they come out strictly ascending, which you can verify on the page.

The decoder reads the embedded timestamp back out of any ULID, and converts between the ULID and UUID renderings of the same 128 bits. That conversion is what makes ULIDs practical against a Postgres uuid column or any other fixed 16-byte storage: the value is identical, only the alphabet changes.

Everything runs client-side using crypto.getRandomValues. Nothing is transmitted, so an identifier generated here has never existed anywhere but your machine.

  • Monotonic generation, so identifiers made in the same millisecond still sort in creation order
  • Bulk output up to 500, as a plain list, JSON, quoted values, CSV or a SQL INSERT
  • Decoder that reads the embedded creation time out of any ULID
  • ULID ↔ UUID conversion, for storing sortable identifiers in a 16-byte uuid column
  • Crockford input rules on decode: case-insensitive, with I and L read as 1 and O as 0
  • crypto.getRandomValues only — Math.random is never used
  • Lowercase output for systems that expect it, without changing the value

How to use it

  1. Choose how many identifiers you need. 500 is the maximum in one go.
  2. Leave Monotonic on unless you have a specific reason not to. It is what guarantees the sort order the format exists for.
  3. Pick an output format. SQL INSERT and CSV are ready to paste into a seed script.
  4. Copy the list, or switch to Decode & convert to read a timestamp out of an existing ULID.
  5. In decode mode you can paste a UUID instead — it is converted to its ULID form automatically.

Real-world use cases

Backend developers

Generate a primary key that sorts by creation time for a new table, instead of adding a separate created_at index to get the same ordering.

Database & platform engineers

Compare ULID against UUIDv7 for a schema migration, and convert an existing value between the two renderings to see exactly what changes and what does not.

QA & test engineers

Decode a ULID pulled from a log line or a bug report to read the exact millisecond it was created, without writing a script for a one-off check.

DevOps engineers

Produce a batch of seed identifiers as a SQL INSERT or CSV for a fixture file or a load-testing script.

Distributed systems engineers

Check how monotonic ordering actually behaves — generate 500 at once and confirm they come out strictly ascending — before relying on it for an event log or message ordering guarantee.

Examples

A ULID and what it contains

Input
01KYM03ZA4P0YS8Y7WEM1XCJ3F
Output
Timestamp:  2026-07-28T09:15:30.500Z
Unix ms:    1785230130500
Randomness: P0YS8Y7WEM1XCJ3F

The first 10 characters are the time. The remaining 16 are 80 random bits — exactly 5 bits per character, with no padding.

Monotonic output within one millisecond

Input
Generate 3, monotonic on
Output
01KYM03ZA4P0YS8Y7WEM1XCJ3F
01KYM03ZA4P0YS8Y7WEM1XCJ3G
01KYM03ZA4P0YS8Y7WEM1XCJ3H

Same timestamp, random component incremented by one each time. Turn monotonic off and these three come out in an arbitrary order relative to each other.

The same identifier as a UUID

Input
01KYM03ZA4P0YS8Y7WEM1XCJ3F
Output
019fa801-fd44-b03d-9478-fc7503d6486f

Identical 128 bits, different alphabet. Converting back returns the original ULID exactly.

Crockford decoding rules

Input
01kym03za4p0ys8y7weMlXCJ3F
Output
01KYM03ZA4P0YS8Y7WEM1XCJ3F

Lowercase is accepted and the letter l is read as the digit 1, because the alphabet has no l to confuse it with. The canonical form is always uppercase.

Common errors

MessageCauseFix
A ULID is 26 characters; this one is 36A UUID was pasted into a field expecting a ULID.In decode mode this tool converts it for you. In your own code, convert explicitly rather than validating loosely — the two formats are different renderings of the same 128 bits, not interchangeable strings.
"U" is not a Crockford base32 characterCrockford base32 excludes I, L, O and U. The first three are excluded because they look like 1, 1 and 0; U is excluded so the encoding cannot accidentally spell obscenities.Check where the value came from. A string containing those letters was not produced by a conformant ULID implementation.
Overflows 128 bits: the first character must be 0-726 base32 characters can hold 130 bits, but a ULID is 128. Anything above 7 in the leading position is a well-formed string that cannot be an identifier.Reject it. Most validators that only check length and alphabet accept these, which is why a value can pass validation and then fail to store in a 16-byte column.
Identifiers from the same request are not in insertion orderThe generator was not monotonic. Within one millisecond the random components are independent, so their order is random.Use a monotonic factory that holds state across calls, as this page does. Calling a stateless ulid() in a loop cannot be monotonic — there is nothing to increment.
invalid input syntax for type uuidA 26-character ULID was inserted into a Postgres uuid column.Convert to the UUID rendering first — the Decode & convert tab does it. The bytes are unchanged, so the column still holds the same identifier and still sorts by creation time.
A converted ULID reports as UUID version 0The UUID form of a ULID does not set the RFC 9562 version and variant nibbles, because doing so would overwrite four bits of the original value and break the round trip.Expected, and unavoidable. Databases store it without complaint; only strict RFC validators object. If you need a valid versioned identifier, use UUIDv7 instead — it solves the same problem with the same layout.

Frequently asked questions

Is anything sent to a server?

No. This ULID generator runs entirely in your browser using crypto.getRandomValues, the same cryptographic source your browser uses for TLS. You can disconnect from the network and it keeps working — which is the only real proof of a claim like this.

ULID or UUIDv7?

They solve the same problem — a time-ordered identifier that does not scatter B-tree inserts — and both use a 48-bit millisecond timestamp. UUIDv7 is the standardised one (RFC 9562) and is understood by every uuid column and library, so it is the safer default. ULID is shorter (26 characters against 36), case-insensitive, has no hyphens to strip, and is safe in a URL without encoding. Pick UUIDv7 for a database primary key, ULID when the identifier is going to be read, typed or put in a path segment.

What does monotonic actually mean here?

That two identifiers generated in the same millisecond are still guaranteed to sort in the order they were created. The timestamp only has millisecond resolution, so anything faster than that relies on the random component, which is unordered by definition. A monotonic generator remembers the last value and adds one instead of redrawing — the guarantee holds within one process, which is where the tight loops that need it happen.

Are ULIDs safe to expose in a URL?

They are as safe as any sequential identifier, which is to say: they leak information. The timestamp is readable by anyone, so a ULID tells its holder when the record was created, and monotonic values created in the same millisecond are adjacent and therefore guessable. That is fine for a public post ID and wrong for a password-reset token. For anything that must be unguessable, use 128 random bits with no structure at all.

How likely is a collision?

Within a single millisecond you are drawing from 2^80 values, about 1.2 septillion. A collision needs two identifiers in the same millisecond to draw the same 80 bits; at a million per millisecond the probability is still around 10^-12. In monotonic mode collisions inside one generator are impossible by construction, since each value is the previous one plus one.

Why 26 characters rather than 25 or 27?

128 bits divided by 5 bits per base32 character is 25.6, so 26 characters are needed and 2 bits are left over. Those spare bits sit at the front, which is why the first character of a valid ULID can only be 0 through 7 — and why a validator that checks length and alphabet alone will accept strings that cannot be stored in 16 bytes.

Can I generate ULIDs for a past or future date?

Not from this page — every identifier gets the current time, because a generator that back-dates identifiers is a good way to corrupt an ordering. The decoder does read any timestamp, so you can verify what a value from your own system encodes. The format runs out at 10889-08-02, when the 48-bit millisecond field overflows.

Does clicking Regenerate continue the monotonic sequence, or start over?

It starts over. Each batch you generate gets its own monotonic counter, so the guarantee holds within the set of identifiers you just produced, not across separate batches, page reloads, or browser tabs. That matches how you'd use a monotonic factory in real code too — one instance per process, not a fresh one per call — so a single generate() of N ULIDs is the right unit to test the ordering against, and it's why the sort check on this page only ever compares within one batch.

Last updated