ulid -- Universally Unique Lexicographically Sortable Identifiers
July 27, 2026 ยท View on GitHub
Description
As described in the ulid specification repo, and slightly edited here, UUID use can be suboptimal for many uses-cases because:
- It is not the most character efficient way of encoding 128 bits of randomness
- UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
- UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
- UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
Instead, an alternative is proposed in ULID:
ulid() // 01ARZ3NDEKTSV4RRFFQ69G5FAV
with the following properties:
- 128-bit compatibility with UUID
- 1.21e+24 unique ULIDs per millisecond
- Lexicographically sortable!
- Canonically encoded as a 26 character string, as opposed to the 36 character UUID
- Uses base32 by Crockford for better efficiency and readability (5 bits per character)
- Case insensitive
- No special characters (URL safe)
- Monotonic sort order (correctly detects and handles the same millisecond)
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48bits 80bits
Components
Timestamp
- 48 bit integer
- UNIX-time in milliseconds
- Will not run out of space until the year 10889 AD.
Randomness
- 80 bits
- Cryptographically secure source of randomness, if possible
Sorting
The left-most character must be sorted first, and the right-most character sorted last (lexical order). The default ASCII character set must be used. Within the same millisecond, sort order is not guaranteed.
What Is Supported:
The following functions are implemented:
ts_generate: Generate ULIDs from timestampsULIDgenerate: Generate ULIDsunmarshal: Unmarshal a ULID into a data frame with timestamp and random bitstring columnsulid: Alias forULIDgenerate
Installation
The package can be installed from CRAN via install.packages("ulid").
Development versions can also be installed from this repository or from
r-universe via
r <- c('https://eddelbuettel.r-universe.dev', 'https://cloud.r-project.org')
install.packages('ulid', repos = r)
Usage
ulid::ULIDgenerate()
## [1] "0001EKRGEEV98QP062VNRX31P2"
(u <- ulid::ULIDgenerate(20))
## [1] "0001EKRGEEV5XMP54RRRWAK318" "0001EKRGEEKX7VC0PF75AZJXHP"
## [3] "0001EKRGEEXENNCQEH4KCH8QAD" "0001EKRGEEY41HJ6GMXRV1BQBA"
## [5] "0001EKRGEE6HVD7ACWZ52MTVCJ" "0001EKRGEEQWXMPXGC0DGQN32B"
## [7] "0001EKRGEE6W13BK92EF1RXYT7" "0001EKRGEE5A31H38NJFGTK8PC"
## [9] "0001EKRGEEG2GXS53QY9F3M0A9" "0001EKRGEEDA3Y6Y0T52WTS6RM"
## [11] "0001EKRGEE5WS2S3D9KY3F5H9Y" "0001EKRGEE24SZW5NATAADAY9Q"
## [13] "0001EKRGEEBEG51QCKXPM8ZS16" "0001EKRGEE1ZC1QY7RCJR9VJ0B"
## [15] "0001EKRGEECJ50Z4FXM4HW6XWG" "0001EKRGEEER84JP8WTXV5DWV8"
## [17] "0001EKRGEEW3ABA82GZSRXN1RB" "0001EKRGEEAA60CYFGR8832JD6"
## [19] "0001EKRGEE6W5ARCFHH6T75FPZ" "0001EKRGEE5WT4XNP7NS69BM3X"
unmarshal(u)
## ts rnd
## 1 2019-07-27 08:21:34 V5XMP54RRRWAK318
## 2 2019-07-27 08:21:34 KX7VC0PF75AZJXHP
## 3 2019-07-27 08:21:34 XENNCQEH4KCH8QAD
## 4 2019-07-27 08:21:34 Y41HJ6GMXRV1BQBA
## 5 2019-07-27 08:21:34 6HVD7ACWZ52MTVCJ
## 6 2019-07-27 08:21:34 QWXMPXGC0DGQN32B
## 7 2019-07-27 08:21:34 6W13BK92EF1RXYT7
## 8 2019-07-27 08:21:34 5A31H38NJFGTK8PC
## 9 2019-07-27 08:21:34 G2GXS53QY9F3M0A9
## 10 2019-07-27 08:21:34 DA3Y6Y0T52WTS6RM
## 11 2019-07-27 08:21:34 5WS2S3D9KY3F5H9Y
## 12 2019-07-27 08:21:34 24SZW5NATAADAY9Q
## 13 2019-07-27 08:21:34 BEG51QCKXPM8ZS16
## 14 2019-07-27 08:21:34 1ZC1QY7RCJR9VJ0B
## 15 2019-07-27 08:21:34 CJ50Z4FXM4HW6XWG
## 16 2019-07-27 08:21:34 ER84JP8WTXV5DWV8
## 17 2019-07-27 08:21:34 W3ABA82GZSRXN1RB
## 18 2019-07-27 08:21:34 AA60CYFGR8832JD6
## 19 2019-07-27 08:21:34 6W5ARCFHH6T75FPZ
## 20 2019-07-27 08:21:34 5WT4XNP7NS69BM3X
(ut <- ts_generate(as.POSIXct("2017-11-01 15:00:00", origin="1970-01-01")))
## [1] "0001CZM6DG2THKSAX3F1SF30E7"
unmarshal(ut)
## ts rnd
## 1 2017-11-01 15:00:00 2THKSAX3F1SF30E7
Millisecond Resolution
As per issue #13 on the upstream repo, time is actually
encoded mostly as time_t leading to second rather than millisecond resolution. Two patches by
Chris Brove also collected in his fork improve on this by using
std::chrono objects internally. In release 0.4.0, we have switched to his fork and extended the
wrapper functions to support this:
> library(ulid)
> gen_ulid <- \(sleep) replicate(5, {Sys.sleep(sleep); generate()})
> u <- gen_ulid(.1)
> df <- unmarshal(u)
> data.table::data.table(df)
ts rnd
<POSc> <char>
1: 2024-05-30 16:38:28.588 CSQAJBPNX75R0G5A
2: 2024-05-30 16:38:28.688 XZX0TREDHD6PC1YR
3: 2024-05-30 16:38:28.789 0YK9GKZVTED27QMK
4: 2024-05-30 16:38:28.890 SC3M3G6KGPH7S50S
5: 2024-05-30 16:38:28.990 TSKCBWJ3TEKCPBY0
>
Author
Suyash Verma wrote the C++ header library ulid.
Chris Bove updated internals to permit sub-second resolution in his fork.
Bob Rudis created the R package, prepared versions 0.1.0 and 0.2.0, and released version 0.3.0 to CRAN.
Dirk Eddelbuettel has been the R package maintainer since release 0.3.1, and added sub-second support.
License
The package is licensed under the MIT License