Short, sortable, privacy-preserving unique identifiers - from a command line, a Go module, or a C module.
Beta. The current prerelease is
v1.0.0-beta.1, for Linux, macOS, Windows, and FreeBSD. Both implementations build and reproduce the shared test vectors, and every component works from the command line, the Go module, and the C module. See project/backlog.md for where it stands.
A random UUID is 36 characters, sorts meaninglessly, and is awkward to read over a phone. Most of the time that buys uniqueness nobody needed.
An identifier built from a timestamp and rendered in a compact base is far shorter, sorts in creation order as plain text, and is still unique enough for the job. When it is not, more components can be mixed in - more time precision, host, user, hardware address, a UUID, or random data - and the same identifier gets as unique as required.
Host and user components are hashed by default, so an identifier does not leak where it came from. A hardware address (%m) is not hashed, so leave it out where that matters.
$ zuid # the default: a timestamp, base 62, to the second
1wqd1q
$ zuid -f='%d%r' # plus six random symbols, for same-second uniqueness
1wqd1wDppAd6
$ zuid -f='%d-%h-%u' -p='1' # millisecond precision, with a hashed host and user
0VRArWn2-rw79Mr05-6UI3mO4y
$ zuid -f='%d%r' -n='3' # three at once, one per line
1wqd2AKtvRnz
1wqd2AQ1mc6f
1wqd2A9xJbEYEvery component except an unhashed name is a fixed width, so identifiers line up in a column, sort as text, and can be split back into their parts by offset.
-
Identifiers are as short as necessary, but no shorter.
-
Sorts by creation time, as text, with no special comparison function.
-
Uniqueness and length are yours to trade off.
-
Batches in one run.
--countprints as many as asked for, and says on stderr when the format made repeats. -
Private by default. Host and user are hashed unless asked otherwise, and
--saltor--salt-fileputs a secret of your own in front of the hash so candidate names cannot be tried against the output. -
A curated set of bases suited to identifiers, with the library's full set of seventy-odd still available.
-
Embeddable, three ways:
-
A standalone command.
-
A permissively licensed Go module.
-
A C module, for nearly anything else. Also permissively licensed.
-
Base conversion is not reimplemented here. It comes from convert-base-v2, so there is one definition of what a base means rather than two that can drift apart.
There are two implementations of the identifier spec, one in Go and one in Zig, and a shared table of test vectors that both have to reproduce. That is more work than one implementation with bindings around it, and it is deliberate: the two check each other, and neither is forced into the other's constraints.
The Go module stays free of cgo and cross-compiles statically. The Zig side produces the command and the C module. Full reasoning is in project/design.md.
See install.md for the packages, the install scripts, and building from source. To work on zuid itself, see development.md.
The command is GPL-2.0-or-later. The Go and C modules are Apache-2.0, so embedding one brings no obligation beyond attribution. license.md says which license covers which directory, and links the full text of each.
Copyright © 2026 Jim Collier [ID: 2უNაɘ«҂թȹɤξπ๙¿ձϖ]
Licensed under the GNU General Public License v2.0 or later
SPDX-License-Identifier:GPL-2.0-or-later
No warranty.
ZUID™ is a trademark of Jim Collier.
