Skip to main content

cuid2

Python PyPi PyPi

CUID2 for Python 3. Next generation GUIDs. Collision-resistant ids optimized for horizontal scaling and performance.

A port of the CUID2 reference implementation by Parallel Drive to Python 3.

📝 Note: Originally taken from https://github.com/overflowdigital before it was unpublished. Thank you to @joshuathompsonlindley for your original contribution!

What is CUID2?

  • Secure: It's not possible to guess the next ID.
  • Collision resistant: It's extremely unlikely to generate the same ID twice.
  • Horizontally scalable: Generate IDs on multiple machines without coordination.
  • Offline-compatible: Generate IDs without a network connection.
  • URL and name-friendly: No special characters.

Why?

For more information on the theory and usage of CUID2, see the following.

Improvements Over CUID

For more information on the improvements of CUID2 over CUID, see the following.

When to Use CUID2

  • Zero Timestamp or Creation-Rate Leakage: Time-ordered formats (uuid.uuid7() in Python 3.14, PostgreSQL 18 uuidv7(), ULID) embed a 48-bit millisecond timestamp in their prefix, allowing anyone with a public ID to read exact creation times and infer business volume or request velocity (RFC 9562 §8). CUID2 hashes nanosecond time, CSPRNG salt, a monotonic counter, and a host fingerprint through SHA3-512, revealing nothing about when or where an ID was generated.
  • HTML, CSS, and Lexical Safety ([a-z][0-9a-z]+): Every CUID2 is guaranteed to start with a lowercase ASCII letter ([a-z]) and contain only [0-9a-z]. By contrast, time-ordered UUIDv7 (019...) starts with 0 until the year 2527 (and 0–9 until year 7541), ULID always starts with a timestamp digit (0–7), UUIDv4 starts with a numeric digit (0–9) 62.5% of the time (10/16 hex values), and NanoID starts with 0–9, _, or - 18.75% of the time (12/64 alphabet chars). CUID2 IDs are always valid unescaped HTML id attributes and CSS selectors, and copy cleanly with a single double-click.
  • Collision Resistance & Entropy Math:
    • 123.6-Bit Default Keyspace (length=24): 1 leading lowercase letter ($26$) followed by 23 base36 characters ($36^{23}$) yields $26 \times 36^{23} \approx \mathbf{1.61 \times 10^{37}}$ possible IDs ($\approx \mathbf{123.6\text{ bits}}$ of entropy)—slightly more than UUIDv4's 122 random bits in a 33% shorter string (24 vs. 36 chars). Reaching a 50% birthday-bound collision probability requires $\sqrt{2 \ln(2) \times 26 \times 36^{23}} \approx \mathbf{4.7 \times 10^{18}}$ IDs (generating 149 million IDs/sec continuously for 1,000 years).
    • Up to 165.0 Bits (BIG_LENGTH = 32): At length=32, the keyspace expands to $26 \times 36^{31} \approx \mathbf{4.54 \times 10^{49}}$ IDs ($\approx \mathbf{165.0\text{ bits}}$; 50% collision bound at $\approx 7.9 \times 10^{24}$ IDs).
    • Histogram De-Biasing: Because $2^{512} \approx 1.34 \times 10^{154}$ is not an exact power of 36, the most-significant base36 digit of a 512-bit SHA3-512 digest is biased toward low digits; CUID2 drops that leading hash character ([1:]) and prepends an independent uniform [a-z] letter so every character position has a uniform distribution.
  • Pre-Fork & Container Snapshot Resilience: Combining os.getpid(), socket.gethostname(), environment variable keys, time.time_ns(), a randomly initialized monotonic counter (INITIAL_COUNT_MAX = 476,782,367, giving $\approx 25,700$ concurrent hosts before a 50% chance of two hosts even starting at the same counter value), and secrets.SystemRandom (os.urandom) provides defense-in-depth against PRNG or counter state collisions across forked worker processes (Gunicorn, uWSGI, Celery) and cloned VM/container snapshots.
  • Polyglot ORM Compatibility (Prisma & Drizzle): Generates 24-character base36 primary keys compatible with TypeScript/Node.js services using Prisma (@default(cuid(2)) / @onCreate(cuid(2))) or Drizzle (@paralleldrive/cuid2), and replaces the deprecated CUIDv1 (cuid) format.
Property CUID2 (cuid2) UUIDv7 (uuid.uuid7) UUIDv4 (uuid.uuid4) ULID (python-ulid) NanoID (nanoid)
Default Length 24 chars (2..32) 36 chars 36 chars 26 chars 21 chars
Alphabet [a-z0-9] (base36) [0-9a-f-] (hex) [0-9a-f-] (hex) Crockford Base32 [A-Za-z0-9_-] (url64)
Starts with Letter? Yes (100% [a-z]) No (0 until yr 2527) Not guaranteed (37.5% a–f) No (always 0–7) Not guaranteed (81.25%)
Double-Click Copyable? Yes No (hyphens) No (hyphens) Yes No (- breaks word)
Entropy / Random Bits ~123.6 bits (165.0 at 32) 74 bits / ms 122 bits 80 bits / ms 126 bits
50% Collision Bound ~4.7 × 10¹⁸ (~7.9 × 10²⁴ at 32) ~1.6 × 10¹¹ / ms ~2.7 × 10¹⁸ ~1.3 × 10¹² / ms ~1.1 × 10¹⁹
Hides Creation Timestamp? Yes (SHA3-512) No (leaks 48-bit ms) Yes No (leaks 48-bit ms) Yes
Fork / Snapshot Safe? Yes (PID + host + CSPRNG) Counter can collide OS CSPRNG only Counter can collide OS CSPRNG only
B-Tree Index Locality Low (uniform hash) High (time-ordered) Low (uniform random) High (time-ordered) Low (uniform random)

When to choose UUIDv7 instead: Because CUID2 hashes its inputs via SHA3-512, IDs are uniformly distributed across the key space. For write-heavy relational tables where B-tree insert locality and 16-byte binary UUID storage matter more than hiding creation timestamps, prefer Python 3.14's uuid.uuid7() or PostgreSQL 18's uuidv7().

Install

pip install cuid2

Usage

For most use cases—including Django, SQLAlchemy, and Pydantic model defaults—use the top-level create_id and is_cuid functions directly:

from cuid2 import create_id, is_cuid

my_cuid: str = create_id()
short_cuid: str = create_id(length=10)

print(is_cuid(my_cuid))  # True

Tip for Django & ORM defaults: Pass default=create_id directly on model fields (e.g., models.CharField(max_length=24, default=create_id)). Because create_id is a top-level function backed by a lazy singleton generator, Django makemigrations can serialize it directly and Cuid() initialization is deferred until the first ID is generated in each worker process.

You can also generate CUIDs directly in the terminal (optionally specifying a custom length):

$ cuid2
$ cuid2 10

If a framework, dataclass, or Django migration requires a zero-argument callable with a custom length, use functools.partial with create_id:

from functools import partial
from typing import Callable
from cuid2 import create_id

short_cuid_generator: Callable[[], str] = partial(create_id, length=10)


def main():
    short_cuid: str = short_cuid_generator()

Note: cuid_wrapper() is deprecated as of v2.1.0 and will be removed in v3.0.0. Use create_id directly for default 24-character IDs or functools.partial(create_id, length=...) for pre-configured custom lengths.

Finally, for more explicit control of the CUID generator, you can instantiate the class directly:

from cuid2 import Cuid

CUID_GENERATOR: Cuid = Cuid(length=10)


def main():
    my_cuid: str = CUID_GENERATOR.generate()
    next_cuid: str = CUID_GENERATOR.generate()

    # Validate CUID
    print(Cuid.is_cuid(my_cuid))  # True

is_cuid (and Cuid.is_cuid) also accepts optional min_length and max_length arguments to tighten the allowed length. The bounds are clamped to 2 and 98 characters, so they can narrow but never widen what is considered valid.

Metadata

Release files for cuid2 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cuid2 2.1.0
File Size Uploaded Provenance Actions
cuid2-2.1.0.tar.gz 12.2 kB Without provenance Details for cuid2-2.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for cuid2 2.1.0
File Interpreter ABI Platform Actions
cuid2-2.1.0-py3-none-any.whl Python 3 none any Details for cuid2-2.1.0-py3-none-any.whl

Total release size: 24.5 kB

Release files / cuid2-2.1.0.tar.gz

Download URL cuid2-2.1.0.tar.gz
Size 12.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ac7b5979ba5c603c94e6311ef3f485deab32466bab7b87efbc56734d955e714f
BLAKE2b-256 checksum
How to use checksums
1a183a973b1d03ca7843eaae4b273359d5ba00a626fbbb9ce716b40df732b59e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / cuid2-2.1.0-py3-none-any.whl

Download URL cuid2-2.1.0-py3-none-any.whl
Size 12.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
59fcc5d82b9048feb200b5296056badd8d78ca947312f22d87aeb0a910a4ef4a
BLAKE2b-256 checksum
How to use checksums
2d192da61612982207b4cdb1cce58c9df7c58c8ebf1a523efd18e1bdcb9f7c59
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for cuid2 2.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page