ULID generator

Generate time-ordered ULID identifiers in your browser with Date.now() and crypto.getRandomValues(). Crockford Base32, locally. Nothing uploaded.

Count accepts 1–100. Click Generate to mint new identifiers. Values outside 1–100 are adjusted to fit that range. After Generate, the field shows the clamped count. Docify's ULID generator creates a 128-bit identifier in your browser as 26 Crockford Base32 characters. The first 10 characters encode a 48-bit Unix millisecond timestamp from Date.now() (leap seconds ignored). That millisecond clock is the same POSIX-ms count as /unix-timestamp/ Milliseconds mode. This page does not convert 1700000000 or extract those 48 bits. The last 16 characters encode 80 bits from crypto.getRandomValues(). This page does not increment the random component when the millisecond repeats, so two IDs from the same millisecond are not ordered by click. Lowercase only reformats the stored strings. It does not generate UUID v4, v5, or v7. Those pages are /uuid-generator/, /uuid-v5-generator/, and /uuid-v7-generator/. RFC 4648 §6 Base32 is /base32/. RFC 4648 §7 base32hex is /base32hex/. Nothing is uploaded.

ULIDs

How it works

  1. You can generate 1 to 100 ULIDs at a time. The count field clamps invalid input to this range.
  2. Each click writes a 128-bit ULID: 10 Crockford Base32 characters from Date.now() (48-bit Unix ms, most-significant digit first) plus 16 characters from 80 CSPRNG bits. The alphabet excludes I, L, O, and U. The first character stays in 0–7. There is no Math.random() fallback and no same-millisecond monotonic increment.
  3. Lowercase only reformats the stored strings with toLowerCase(). The spec is case-insensitive; the canonical form is uppercase. Copy writes the formatted lines. Clear empties the list. The page does not decode a pasted ULID.

ULID vs UUID v7

Both identifiers are 128 bits with a leading 48-bit Unix millisecond timestamp, so values minted in different milliseconds sort by time. The difference is encoding and how the remaining bits are spent. A ULID is 26 Crockford Base32 characters (80 random bits, no version or variant field). UUID v7 is RFC 9562 §5.7: hyphenated hex with version nibble 7, variant 10xx, and 74 random bits. This page does not convert a ULID into a UUID or the reverse. For a v7 string, use the UUID v7 generator. For how v7 compares to v4 and v5, see UUID Versions Explained: v4 vs v1, v5, v7 and v8 (Format & Length).

AspectULIDUUID v7
SpecCommunity ULID specRFC 9562 §5.7
Layout48-bit Unix ms + 80 random bits48-bit Unix ms + version 7 + variant 10 + 74 random bits
Text form26 Crockford Base32 characters, no hyphens36 hex characters with hyphens (32 without)
Alphabet0–9 A–Z except I, L, O, U0–9 a–f (canonical lowercase hex)
Same-ms orderNo increment on this pageNo RFC 9562 §6.2 counter on /uuid-v7-generator/

This generator does not increment the 80-bit random field when Date.now() repeats, so two ULIDs from the same millisecond are not ordered by click. The v7 page also skips RFC 9562 §6.2 monotonic methods. Both encodings leak the minting millisecond. Neither string is a password, session token, or signed claim. Crockford Base32 is not RFC 4648 §6 Base32 (A–Z 2–7) or §7 base32hex (0–9 A–V) — those pages are Base32 and Base32hex.

ULID in Python and Node

The snippets below encode the same 128-bit layout this page mints: 48-bit Unix milliseconds as 10 Crockford Base32 characters, then 80 CSPRNG bits as 16 more. Node and CPython have no stdlib ULID API — there is no crypto.ulid and no uuid.ulid. The Node snippet uses Date.now() and crypto.randomBytes — the same POSIX-ms clock and CSPRNG family this page runs through crypto.getRandomValues(). CPython uses time.time_ns and secrets.token_bytes. There is no POSIX ULID generator. crypto.randomUUID() and CPython uuid.uuid4 are RFC 9562 v4. CPython 3.14+ uuid.uuid7 is RFC 9562 §5.7 hyphenated hex, not Crockford. For how v7 compares to v4 and v5, see UUID Versions Explained: v4 vs v1, v5, v7 and v8 (Format & Length).

Node.js with crypto.randomBytes

Confirmed here with Node 22. encodeTime(1469918176385) is the spec vector 01ARYZ6S41. Live minting should start from randomBytes(10), not zeros. Also import { randomBytes } from 'node:crypto'. This is not crypto.randomUUID():

const { randomBytes } = require('crypto');
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';

function encodeTime(ms) {
  let now = ms;
  let str = '';
  for (let i = 0; i < 10; i += 1) {
    const mod = now % 32;
    str = CROCKFORD.charAt(mod) + str;
    now = (now - mod) / 32;
  }
  return str;
}

function encodeRandom80(bytes) {
  let acc = 0;
  let bits = 0;
  let out = '';
  for (let i = 0; i < bytes.length; i += 1) {
    acc = acc * 256 + bytes[i];
    bits += 8;
    while (bits >= 5) {
      bits -= 5;
      const digit = Math.floor(acc / 2 ** bits) & 31;
      acc %= 2 ** bits;
      out += CROCKFORD.charAt(digit);
    }
  }
  return out;
}

encodeTime(1469918176385)
// "01ARYZ6S41"

encodeTime(1700000000000) + encodeRandom80(Buffer.alloc(10))
// "01HF7YAT000000000000000000"

encodeTime(Date.now()) + encodeRandom80(randomBytes(10))
// 26 Crockford chars; first character is 0-7; bits 0-47 are Date.now()

Python with secrets.token_bytes

CPython has no uuid.ulid. Integer division matches this page's (now - mod) / 32 on 48-bit timestamps. time.time_ns() // 1_000_000 is that same POSIX-ms clock:

import secrets
import time

CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'

def encode_time(ms):
    now = int(ms)
    chars = []
    for _ in range(10):
        mod = now % 32
        chars.append(CROCKFORD[mod])
        now = (now - mod) // 32
    return ''.join(reversed(chars))

def encode_random80(data):
    acc = 0
    bits = 0
    out = []
    for b in data:
        acc = acc * 256 + b
        bits += 8
        while bits >= 5:
            bits -= 5
            digit = (acc // (2 ** bits)) & 31
            acc %= 2 ** bits
            out.append(CROCKFORD[digit])
    return ''.join(out)

encode_time(1469918176385)
# '01ARYZ6S41'

encode_time(1700000000000) + encode_random80(bytes(10))
# '01HF7YAT000000000000000000'

encode_time(time.time_ns() // 1_000_000) + encode_random80(secrets.token_bytes(10))
# 26 Crockford chars; first character is 0-7

The layout string uses ten zero bytes so the 48-bit field is visible; Generate never writes that all-zero tail. 1700000000000 is 1700000000 seconds — 2023-11-14T22:13:20.000Z. Both use the same POSIX-ms clock as Unix timestamp Milliseconds mode. Ten bytes are exactly 80 bits, so there is no remaining bit after 16 Crockford digits. This page does not increment the 80-bit field when Date.now() repeats, so two IDs from the same millisecond are not ordered by click. The first character stays in 0–7 because 26 Base32 digits can hold 130 bits and a ULID uses 128; the largest valid encoding is 7ZZZZZZZZZZZZZZZZZZZZZZZZZ. npm ulid and PyPI python-ulid are other packages, not this page, and some of those mint monotonically inside a millisecond — this generator does not. crypto.randomUUID() and uuid.uuid4 belong on the UUID v4 generator. uuid.uuid7 belongs on the UUID v7 generator. Crockford Base32 is not RFC 4648 §6 Base32 (A–Z 2–7) or §7 base32hex (0–9 A–V) — those pages are Base32 and Base32hex. There is no POSIX ULID generator; uuidgen and CPython python -m uuid write UUID v4, not Crockford. The page does not decode a pasted ULID. The high bits leak the minting millisecond; this is not a password hasher.

FAQ

Does this generator upload anything?
No. Each ID is created in your browser with Date.now() and crypto.getRandomValues(). The page does not send the strings to a server.
What count range does Generate accept?
Count accepts 1–100. Click Generate to run the conversion. Values outside 1–100 are adjusted to fit that range. After Generate, the field shows that clamped integer. Each Generate click mints that many ULIDs.
What layout does this produce?
A 128-bit ULID encoded as 26 Crockford Base32 characters (0123456789ABCDEFGHJKMNPQRSTVWXYZ — no I, L, O, or U). The first 10 characters are a 48-bit Unix millisecond timestamp from Date.now() (leap seconds ignored). The last 16 characters are 80 bits from crypto.getRandomValues(). The first character is 0–7 because 26 Base32 digits can hold 130 bits and a ULID uses 128; the largest valid encoding is 7ZZZZZZZZZZZZZZZZZZZZZZZZZ. Lowercase only reformats stored strings. It does not generate UUID v1, v4, v5, or v7. Those pages are /uuid-generator/, /uuid-v5-generator/, and /uuid-v7-generator/. RFC 4648 §6 Base32 (A–Z 2–7) is /base32/. RFC 4648 §7 base32hex (0–9 A–V) is /base32hex/.
Are successive ULIDs strictly monotonic?
Not within the same millisecond. This page does not increment the 80-bit random component when Date.now() repeats. IDs minted in different milliseconds sort by that 48-bit timestamp. Two clicks in the same millisecond can sort in either order because the 80 random bits are fresh CSPRNG output. Date.now() is the same POSIX-ms clock as /unix-timestamp/.
Does this generator convert a Unix timestamp?
No. Date.now() is the same POSIX-ms clock as /unix-timestamp/ Milliseconds mode. This page encodes those 48 bits as the first 10 Crockford characters; it does not convert 1700000000, does not decode a pasted ULID, and does not extract those bits. The /unix-timestamp/ tool can convert timestamps like 1700000000 seconds to 2023-11-14T22:13:20.000Z.
Are these IDs secrets or proof of uniqueness?
No. A ULID is a 48-bit clock plus 80 bits of CSPRNG output. This page does not check a registry, mint a database sequence, or treat the string as a password, session token, or signed claim. The high bits leak the minting millisecond. The page does not decode a pasted ULID.

Related