# rs/xid: a 12-byte sortable ID generator for Go services

> rs/xid generates 96-bit, base32hex-encoded IDs with no machine or data center configuration. It fits request IDs and database keys, but it is not a random ID generator.

**rs/xid** — xid is a globally unique id generator thought for the web

- Repository: https://github.com/rs/xid
- Stars: 4,282 · Forks: 217
- Language: Go
- License: MIT
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/rs-xid

## What rs/xid solves, and who it is aimed at

The README describes xid as a globally unique id generator library, ready to safely be used directly in your server code. The problem it addresses is the gap between UUID and Twitter Snowflake. UUIDs are 16 bytes and 36 characters as strings, and the README's comparison table marks them configuration free but not sortable. Snowflake IDs are 8 bytes but the table says they need machine and data center configuration and a central server. xid sits between the two: 12 bytes, 20 characters in its string form, sortable, and requiring no configuration.

The intended audience is Go server developers who want an ID they can generate inline without coordinating a machine ID across a fleet. The README states that no configuration or central generator server is required so it can be used directly in server's code. That is the whole pitch. If you are already running a coordination service for ID allocation, xid is solving a problem you have already paid to solve.

The README also names zerolog's RequestIDHandler as the best use case, which tells you something about the expected workload: one ID per request, generated on the hot path, logged and then propagated. That is a different job from generating identifiers for a distributed database that must remain unique across regions with clock skew.

## How the 12 bytes are laid out: time, machine, pid, counter

xid reuses the Mongo Object ID algorithm with a different serialization. The README lists the fields explicitly: 4 bytes of seconds since the Unix epoch, 3 bytes of machine identifier, 2 bytes of process id, and a 3-byte counter that starts with a random value. That is 12 bytes total, and the README notes the binary representation is compatible with Mongo 12-byte Object IDs.

The string form is base32hex without padding, which the README explains was chosen over base64 because case sensitivity and the two non-alphanumeric characters may be an issue when transported between systems. Base36 was rejected because it is not standard, its size is not bit aligned, and it would not remain sortable. The hex variant of base32 keeps the sortable property. Validation is therefore a fixed pattern: 20 characters, all lowercase, drawn from a to v and 0 to 9, which the README writes as [0-9a-v]{20}. If you are writing a database check constraint or a regex in another language, that is the pattern to copy.

Sortability comes from the leading timestamp, and the README states the embedded time has 1 second precision. That is coarser than you might assume. Two IDs generated in the same second are ordered by their machine, pid and counter bytes, not by wall-clock sub-second time. The README claims unicity is guaranteed for 16,777,216 (24 bits) unique ids per second and per host/process, which is the ceiling implied by the 3-byte counter.

Generation is described as lock-free, unlike UUIDv1 and v2, and the README's benchmark table shows xid at 91.1 ns/op with 1 allocation against UUIDv4 at roughly 1500 ns/op with 2 allocations on the same machine. Those numbers come from the README's own benchmark run, not from an independent measurement, and the README itself notes UUIDv1 requires a global lock.

## Installing rs/xid and generating your first ID

The install step is a single go get against the module path. The repository's go.mod declares the module as github.com/rs/xid and targets go 1.16, so the module path in the command is the one to use.

```bash
go get github.com/rs/xid
```

After that, generating an ID is one call. The README's usage example assigns xid.New() to a variable and prints its string form, showing 9m4e2mr0ui3e8a215n4g as the output. That value is 20 characters long and matches the [0-9a-v]{20} pattern.

```go
guid := xid.New()

println(guid.String())
// Output: 9m4e2mr0ui3e8a215n4g
```

Once you have an ID, the README shows four accessors that expose the embedded fields: guid.Machine(), guid.Pid(), guid.Time() and guid.Counter(). These are useful when you want to log which host and process produced a request ID, or when you want to recover the creation time without storing a separate timestamp column. The README does not document a parse function or an error type in the usage section, though the repository does contain error.go, so if you need to decode an incoming string you should read that file rather than assume the API.

The one piece of configuration the README does mention is the machine identifier. It states that MachineID can be set by the environmental variable XID_MACHINE_ID to allow fine tune control over the generation. Everything else, including the process id and the counter seed, is handled for you.

## Where xid is the wrong tool: predictability and clock dependence

The README is unusually direct about this. It states that xid is dependent on the system time, a monotonic counter and so is not cryptographically secure, and that if unpredictability of IDs is important, you should not use xids. It then adds that most other UUID-like implementations are also not cryptographically secure, which is true but does not change the conclusion for your use case. If an attacker can observe one ID and guess the next, xid is the wrong choice, and the README points you at crypto/rand or /dev/urandom instead.

There is a second failure mode the README only implies. Because the leading 4 bytes are seconds since the Unix epoch, xid leaks creation time to anyone who holds the string, and the README's Time() accessor makes that explicit rather than accidental. If your IDs appear in URLs or API responses and creation time is sensitive, that is a disclosure you are choosing to make. The 1-second precision also means the sort order is not a reliable event ordering within a second; do not use an xid as a sequence number for events that arrive faster than that.

The machine identifier is 3 bytes and, per the README, is configurable through XID_MACHINE_ID. If you run many hosts and never set that variable, uniqueness across hosts depends on whatever the platform-specific hostid files derive, and the repository carries separate implementations for darwin, freebsd, linux, openbsd and windows plus a fallback. That is a real constraint if you containerize: containers on the same host may or may not present the same machine identifier, and the README does not discuss container behavior. Test uniqueness across your own deployment rather than assuming it.

## rs/xid against UUID and Snowflake: the actual trade-off

The README's own comparison table is the clearest statement of the difference. UUID is 16 bytes and 36 characters, configuration free, not sortable. shortuuid is also 16 bytes and 22 characters, configuration free, not sortable. Snowflake is 8 bytes, up to 20 characters, sortable, but needs machine and data center configuration and a central server. MongoID is 12 bytes and 24 characters, configuration free, sortable. xid is 12 bytes and 20 characters, configuration free, sortable.

Against UUID, the difference is size and ordering, not safety. A UUIDv4 is drawn from a random source, so it leaks nothing and orders nothing. xid is 4 bytes smaller in binary and 16 characters shorter as a string, and it sorts by creation time. If you are indexing millions of rows in a B-tree, that ordering is the reason to pick xid over UUIDv4: inserts land near the end of the index instead of scattering. If you need IDs that reveal nothing, UUIDv4 is the better default and xid is a downgrade.

Against Snowflake, the difference is operational. Snowflake gives you 8 bytes and up to 20 characters, but the README says it requires machine and data center configuration and a central generator server. xid trades 4 extra bytes to avoid that coordination entirely. If you already run a Snowflake allocator, or you need more than 16,777,216 IDs per second per process, xid's 3-byte counter is the binding limit and Snowflake's larger bit budget is the reason to stay. The README does not compare xid to ULID, so treat any ULID comparison you read elsewhere as outside this project's documentation.

## Maintenance, licensing and the cost of upgrading

The repository is not archived and the last push was on 2026-09-12, so the tree is being touched. The release history tells a different story: the newest tag listed is v1.2.1 from 2018-08-30, described as removing the dependency on x/sys, with v1.1.0 from 2018-05-23 and v1.1 from 2017-06-07 before it. There is a gap of roughly eight years between the newest tag and the most recent commit. If you depend on a tagged release, you are depending on code from 2018; if you depend on master, you are depending on unreleased commits. Neither is wrong, but you should know which one you are doing, and go.mod is a good place to check what your build actually resolves.

The upgrade cost is low by construction. The module has no dependencies: go.mod declares only the module path and go 1.16, and the v1.2.1 release note is specifically about removing one. There is no transitive tree to audit and no version conflict to resolve. The API surface visible in the README is a constructor and four accessors, which is small enough that a breaking change would be easy to spot.

The licence is MIT, per the README badge and the LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and permission notice are retained, and it comes with no warranty. That is a summary of what the licence family generally means, not legal advice; read the LICENSE file yourself if the distinction matters to your organization. The README also links a long list of ports in other languages, from Python and Rust to PostgreSQL and Swift, and those ports carry their own licences, which the README does not state.

## Conclusion

Adopt rs/xid when you need short, sortable, configuration-free IDs in Go and can accept that they are time-ordered rather than unpredictable; the README states plainly that xid is not cryptographically secure, so anything a user could guess (session tokens, password reset links, capability URLs) should use crypto/rand instead. The last push was on 2026-09-12, but the latest tagged release is v1.2.1 from 2018-08-30, so pin a commit or tag and read id.go rather than assuming the tagged version matches the current tree. Verify two things before rolling it out: that your storage column is case-sensitive and 20 characters wide for the string form, and that your consumers can handle a 12-byte binary value if you ever send the raw bytes rather than the string.

## FAQ

### How do I install rs/xid in a Go project?

Run go get github.com/rs/xid, which matches the module path declared in the repository's go.mod. The README's install section gives that single command and nothing else is required.

### Is an rs/xid cryptographically secure?

No. The README states that xid is dependent on the system time and a monotonic counter and is not cryptographically secure, and that you should not use xids if unpredictability of IDs is important. It points to crypto/rand or /dev/urandom for truly random IDs.

### How does rs/xid compare to UUID and Snowflake?

The README's table lists xid at 12 bytes and 20 characters, configuration free and sortable, against UUID at 16 bytes and 36 characters and not sortable, and Snowflake at 8 bytes with up to 20 characters but requiring machine and data center configuration and a central server. xid sits between them.

### Can I control the machine identifier used by rs/xid?

Yes. The README states that MachineID can be set by the environmental variable XID_MACHINE_ID to allow fine tune control over the generation. Otherwise the machine identifier is derived by the platform-specific hostid files in the repository.

### What does a valid rs/xid string look like?

The README says to expect a 20 character long, all lowercase sequence of a to v letters and 0 to 9 numbers, written as [0-9a-v]{20}. The usage example output is 9m4e2mr0ui3e8a215n4g.

## Sources

- [Issues](https://github.com/rs/xid/issues)
- [License: MIT](https://github.com/rs/xid/blob/master/LICENSE)
- [README](https://github.com/rs/xid/blob/master/README.md)
- [Releases](https://github.com/rs/xid/releases)
- [rs/xid on GitHub](https://github.com/rs/xid)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rs-xid
