Cysharp Ulid: a sortable identifier for .NET and Unity
Fast .NET C# Implementation of ULID for .NET and Unity.
At a glance
- What is it?
- Cysharp's Ulid is a C# implementation of the ULID spec whose API deliberately mirrors System.Guid, with a MessagePack-based binary form and a dotnet global tool for generating identifiers from the command line. It suits .NET teams that want time-ordered primary keys, and it is the wrong tool for a stack that has no need for sortability in the key itself.
- Who is it for?
- Cysharp Ulid is worth adopting for .NET Core or Unity services that want lexicographically sortable identifiers, whether as primary keys or as opaque tokens, because the API mirrors Guid closely enough that the migration cost is small and the string form is the standard ULID one.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The API is a deliberate copy of Guid, method for method
Cysharp's Ulid is a C# implementation of the ULID specification for .NET Core and Unity, and the design decision that makes adoption cheap is that the surface area mirrors System.Guid. The README lists the members to know: Ulid.NewUlid() to create one, Ulid.Parse() and Ulid.TryParse() to read one back, the new Ulid() constructor, and then ToString(), ToByteArray(), TryWriteBytes(), TryWriteStringify() and ToBase64(). Two properties expose the internals, .Time and .Random. A developer who has used Guid can start calling the same-shaped methods without learning a new mental model, and code that stores identifiers as strings or byte arrays can often be adapted by changing the type name alone.
Conversion to Guid exists, which is a pragmatic escape hatch
The README states that conversion to Guid is allowed, and lists .ToGuid() as a method and an explicit cast from a Ulid to a Guid. This matters more than it first appears. A large existing system usually has hundreds of places typed as Guid, from entity identifiers to method signatures, and a library that forces a rewrite of all of them will be rejected on cost alone. Being able to convert at the boundary means a team can introduce Ulid for new identifiers and keep the rest of the codebase untouched, or use it in a subsystem that benefits from sortability while the database columns stay Guid. The trade is that a converted Guid no longer carries the timestamp in a form the database can index for time, so the conversion is a compatibility bridge rather than a free win.
The claimed speed advantage comes from the MessagePack binary form
The README states that the project aims at performance through the fastest binary serialiser technology, MessagePack-CSharp, and that it achieves faster generation than Guid.NewGuid(). The benchmark tables in the README compare three implementations, the framework Guid, this library, and NUlid from RobThree, across seven operations. On New, the README records 73.13 ns for Guid, 65.41 ns for Ulid and 209.89 ns for NUlid. On Parse, 197.98 ns for Guid, 28.67 ns for Ulid and 161.03 ns for NUlid. On ToString, 57.73 ns, 38.77 ns and 96.76 ns respectively. The NewId().ToString() combined case is where the gap widens, at 205.7 ns for Guid against 110.2 ns for Ulid, which the README describes as twice as fast.
Allocation behaviour is the more useful half of the numbers
Beyond raw speed, the tables carry a second set of columns, Gen 0/1k Op, Gen 1/1k Op, Gen 2/1k Op and Allocated Memory/Op, and those columns are where the design shows. New and Parse allocate nothing at all for both Guid and Ulid, while NUlid allocates 104 B and 280 B on the same operations. On ToString, Ulid allocates 80 B against Guid's 104 B and NUlid's 368 B. GetHashCode and Equals matter because they run when a value is used as a dictionary key, and there the Ulid figure of 1.0329 ns is close to Guid's 0.9706 ns while NUlid is an order of magnitude slower at 20.6175 ns. CompareTo, which runs during a sort, records 3.838 ns for Ulid against 5.409 ns for Guid and 17.126 ns for NUlid. These are the author's own numbers on their own machine, so treat them as relative rather than absolute.
A dotnet global tool generates identifiers without writing code
The project ships a command line tool, installed as a.NET Core global tool.
dotnet tool install --global ulid-cliOnce installed, it prints a single identifier by default.
$ dotnet ulid
01D7CB31YQKCJPY9FDTN2WTAFFThe interesting part is the timestamp and randomness controls, which are how you construct boundary values for testing. The -t flag takes a timestamp that is converted to UTC, and combined with -min or -max it produces the lowest or highest possible identifier for that moment.
$ dotnet ulid -t "2019/03/25" -min
01D6R3EBC00000000000000000
$ dotnet ulid -t "2019/03/25" -max
01D6R3EBC0ZZZZZZZZZZZZZZZZAdding -base64 switches the output encoding, and the CLI accepts -r for 16 characters of randomness, -minRandomness and -maxRandomness as named flags. The tool is powered by ConsoleAppFramework, another project by the same author.
JSON support is a separate package rather than a built-in
The Integrate section of the README points to a distinct NuGet package, Ulid.SystemTextJson, for System.Text.Json support, and the README says you can use a custom Ulid. Keeping the serializer integration out of the core package is the right call for dependency hygiene, since the core library targets both.NET Core and Unity and the JSON integration is optional. It does mean the install story is two packages for a typical service. The repository layout backs up the structure of a serious library rather than a single project: a solution file, a Directory.Build.props for shared build settings, a benchmark directory, a sandbox directory, a tests directory, a signing key file for releases, and an.editorconfig for style.
MIT licensed, and the release cadence is uneven
The licence is MIT, which permits commercial use and redistribution inside a proprietary product as long as the copyright notice is kept, and the project is published to NuGet as Ulid, with a.unitypackage also available from the Releases page for Unity users. The release history shows v1.4.1 on 2025-07-28, v1.4.0 on 2025-07-25 and v1.3.5 also on 2025-07-25, three tags inside four days. The last push to the repository was on 2026-10-07, which is well after those tags, so work is landing in the tree without a corresponding release. The version numbers staying under 2.0 after several years is itself informative: the API has been stable enough that the author has not needed a breaking change, but a library that has not shipped a release in over a year is a factor to weigh if you need fixes.
Editorial conclusion
Cysharp Ulid is worth adopting for .NET Core or Unity services that want lexicographically sortable identifiers, whether as primary keys or as opaque tokens, because the API mirrors Guid closely enough that the migration cost is small and the string form is the standard ULID one. It is not a fit if you only need uniqueness, since Guid.NewGuid is simpler and has no timestamp component, or if your storage is not a byte-order-preserving string, since the sortability depends on the ULID layout. Verify first which target frameworks the installed NuGet package supports for your runtime, and check the MessagePack dependency in your build, since the binary serialisation path depends on it.
Frequently asked questions
What is Cysharp Ulid used for?
It is a C# implementation of the ULID specification for .NET Core and Unity, producing identifiers that embed a timestamp so they sort in creation order. The API mirrors System.Guid, and conversion to and from Guid is supported.
How do I install the Ulid package?
Through NuGet, with the Install-Package Ulid command, or by downloading a .unitypackage from the repository Releases page for Unity. The JSON integration for System.Text.Json lives in a separate package called Ulid.SystemTextJson.
Is Ulid faster than Guid.NewGuid?
The README's own benchmarks record 65.41 ns for Ulid.NewUlid against 73.13 ns for Guid.NewGuid, and the parse case is far wider at 28.67 ns against 197.98 ns. These are the author's measurements on their own machine rather than independent results.
How do I generate a ULID from the command line?
Install the global tool with dotnet tool install --global ulid-cli and run dotnet ulid. Flags include -t for a timestamp, -min and -max for the lowest and highest randomness values, -r for 16 characters of randomness, and -base64 to change the output encoding.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cysharp-ulid)Was this useful?