Introducing ZLID

S

Shawn Seymour

03-26-2026
6 min

Identifiers look like plumbing.

Then they are in your primary keys, your indexes, your URLs, your logs, your screenshots, your support queue, and your public API. By the time you realize your ID format was an architecture decision, you have already made it.

I built ZLID for that moment.

01K2R7KFWE5807000000000001

That is a ZLID.

If a human is reading it in a ticket or over a call, you might visually chunk it as:

01K2R7KF-WE5807-000000-000001

Canonically, though, ZLID is one 26-character uppercase string with no separators.

And if that same underlying row needs a public face, it can look like this instead:

14MZBHPE0WKEQQR9KDP4EF9696

And if you want a public identifier with no built-in relationship back to the internal one, it can look like this:

014D2PF2DBSQQG28T5CY4TQKF5

That split is the center of the project.

ZLID is an identifier system for real application work: one ordered ID for storage, one optional public alias for that same underlying identity, and one standalone random family when you want a public identifier with no reversible relationship at all.

When to reach for it

ZLID is not only for the team doing exotic ID gymnastics.

If you just need one strong internal ID, ZLID is already enough: ordered, binary-first in storage, URL-safe when rendered, and explicit about behavior.

If the same row also needs a public opaque reference, add ZLID-A.

If you want a public handle with no built-in link back to the internal one, use ZLID-R.

And if you are one of the systems that really can burn through IDs inside the same millisecond, there is a high-throughput ordered profile for that too.

Most teams can stop at the first sentence. That is part of the point.

The first idea: order and spread both matter

A perfectly monotonic key is beautiful until every fresh insert wants the same right-most page.

Anyone who has spent enough time around Kafka learns the same lesson in a different form: order and spread are both valuable, and pretending you never trade between them is how systems get weird.

ZLID puts a partition byte on the wire.

That gives applications a small, explicit dial. Keep the cleanest ordering story you can. Or diffuse writes across nearby ranges within the same millisecond when that is the healthier choice for the storage engine.

It is not a shard ID in disguise. It is opaque on purpose. It exists to shape write behavior, not to gossip about topology.

And the trade is named, not hidden: if you use multiple partition values, same-millisecond ordering becomes partition-grouped rather than one pure issue stream across the whole keyspace.

The second idea: one identity, more than one face

If you build enough SaaS, you eventually learn that an internal row ID and a public reference do different jobs.

That is not the new idea.

The new idea is letting the same identity take different forms on purpose.

In ZLID, that identity can be ordered for storage, opaque for exposure, or fully separate when no relationship should exist at all.

That is what ZLID-A and ZLID-R are really about.

Not just “internal ID versus public ID.” A cleaner identity model.

ZLID-A is a keyed, reversible alias of an ordered ZLID: deterministic within a key-and-tweak domain, reversible under the key, and not meaningfully time-sortable.

It is not an auth token. It is not a capability. It is not security theater.

It is a public face for the same underlying identity.

The metal

The default ordered profile is:

[ 48 ts_ms ][ 8 partition ][ 12 seq ][ 56 rand ][ 4 tag ]

The high-throughput ordered profile is:

[ 48 ts_ms ][ 8 partition ][ 16 seq ][ 52 rand ][ 4 tag ]

The timestamp gives time locality. The sequence gives strict monotonicity. The random tail gives collision safety. The tag makes the on-wire meaning explicit. The partition byte gives the storage engine a vote.

Default is the general-purpose profile. High-throughput spends four random bits to buy more same-millisecond sequence space. Use one ordered profile per logical ordered domain. Do not mix them and pretend they tell one clean creation-order story.

Clamp is not a footnote

Clocks move backward. Specs usually mumble. ZLID does not.

If the clock regresses, generation clamps to the last seen millisecond and marks the value as clamped.

If the sequence overflows inside one millisecond, generation carries into the next millisecond instead of busy-waiting.

That behavior is part of the contract, not folklore buried in one implementation.

I wanted the format to say what happens when the world gets messy, not just what the happy-path bits look like.

Why not just use UUID, ULID, or UUIDv7?

Sometimes you should.

UUID solved minting. ULID made ordered IDs pleasant to look at. UUIDv7 pulls time order into the standard.

ZLID is after a narrower, more opinionated thing: an ID with a storage model, a public-ID model, and an explicit operational contract.

That is its lane.

What it looks like in code

const gen = ZLID.generator({ profile: "default" });

// ordered internal ID
const userId = gen.next();

// public alias of that same user
const publicUserId = ZLID.alias(userId, {
  key: aliasKey,
  tweak: "users|prod",
});

// standalone public ID with no reversible link back
const inviteId = ZLID.random();

That is the model in practice:

  • ordered where storage cares
  • aliased where exposure cares
  • random where no relationship should exist

What v0.1 does not try to do

v0.1 is deliberately narrow.

It standardizes the ordered family, the alias family, the random family, and the two ordered profiles.

It does not standardize compact 24-character IDs. It does not standardize a standards-based reversible alias mode. It does not standardize on-wire key identifiers.

Good.

First releases should be hard to misread.

Compact may come later. A standards-based reversible alias family may come later. Key-rotation metadata may come later. But those should arrive explicitly, not by smearing ambiguity across the first version.

Why I built it

Because identifiers are not leftover plumbing.

They are part of your storage model. Part of your API. Part of your operational story. Part of what your system reveals by accident.

By the time an ID format feels expensive, it is usually already deeply embedded.

So I wanted something more deliberate.

Ordered internally. Readable canonically. Honest under clock drift. Useful for teams that just need one good ID. Sharp enough for teams that need to split internal identity from public exposure.

That is ZLID.

Not a vanity format. Not a prettier UUID.

An identifier system built on purpose.

And the shortest honest version is this:

ZLID is what it looks like when you treat identifiers as infrastructure.