UUIDs, ULIDs and Sqids: A Practical Deep Dive - Wendell Adriel
320;<br>},<br>scrollToTop() {<br>window.scrollTo({ top: 0, behavior: 'smooth' });<br>},<br>updateCurrentTime() {<br>this.currentTime = new Intl.DateTimeFormat([], { hour: '2-digit', minute: '2-digit' }).format(new Date());<br>},<br>initClock() {<br>this.updateCurrentTime();<br>setInterval(() => this.updateCurrentTime(), 60000);<br>},<br>}"<br>x-init="updateScrollTopVisibility(); initClock()"<br>@scroll.window="updateScrollTopVisibility()"
newsletter
650+ subscribers
Get the monthly newsletter in your inbox
Each chapter brings new articles, project updates, and links worth your time for building better applications.
Every subscriber gets a 50% off coupon for my Laravel enterprise ebook.
Email address
Join the Newsletter
Blog Post
blog / uuids-ulids-and-sqids-a-practical-deep-dive
UUIDs, ULIDs and Sqids: A Practical Deep Dive
Author
Wendell Adriel
Published
Jul 6, 2026
Read Time
17 minutes
backend
architecture
php
Contents
Table of contents
Introduction
The Problem We Are Actually Solving
The Quick Comparison
UUIDs: The Standard Workhorse
UUIDv4
UUIDv7
ULIDs: Compact and Lexicographically Sortable
ULID Monotonicity
Sqids: Generated IDs From Numbers
Sqids and Canonical IDs
How They Behave in Databases
Storing UUIDs and ULIDs
Security and Privacy
Public IDs and Internal IDs
A Practical Decision Flow
A Small PHP Abstraction
Common Mistakes
Conclusion
Content
Article
read
Introduction
Choosing an identifier looks like a small decision until the application starts growing around it.
At first, an auto-incrementing integer feels enough. It is small, fast, easy to read, and every<br>relational database understands it. Then the requirements start changing:
you need to create records in different services without asking one database for the next ID
you want public URLs that do not expose sequential database IDs
you want IDs that sort roughly by creation time
you need identifiers that can be generated before a row exists
you want event IDs, request IDs, job IDs, or import IDs that work across systems
That is usually when UUIDs , ULIDs , and Sqids enter the conversation.
They are often discussed as if they solve the same problem, but they do not. They overlap in some<br>places, but their mental models are different:
UUIDs are standardized 128-bit identifiers designed for distributed uniqueness.
ULIDs are 128-bit identifiers with a timestamp-first layout and a compact sortable string<br>representation.
Sqids are short, URL-safe IDs generated from non-negative numbers, usually existing integer<br>primary keys.
In this article, let's do a practical deep dive into how they work, what trade-offs they bring, and<br>when I would use each one in a real application. The examples will use PHP, but the ideas are<br>language and framework agnostic.
The Problem We Are Actually Solving
Before choosing a format, we need to be clear about the job of the identifier.
An identifier can be used for different things:
a database primary key
a public route parameter
an event ID
a trace or correlation ID
an idempotency key
a temporary token
a reference shown to users
Those jobs have different requirements.
For example, a database primary key should be compact, indexed efficiently, and stable forever. A<br>public URL ID should be safe to expose and pleasant enough to copy. An event ID should be globally<br>unique without central coordination. A security token should be unguessable and should usually not be<br>decodable.
This is why the question should not be:
Which one is the best ID?
The better question is:
What properties does this identifier need in this part of the system?
Once we ask that, the comparison becomes much clearer.
The Quick Comparison
Let's start with the high-level view.
UUIDv4: 36 characters, generated from randomness, not sortable by time, not decodable, and best<br>used for distributed opaque IDs.
UUIDv7: 36 characters, generated from timestamp plus randomness, sortable by time, partially<br>decodable because the timestamp is part of the value, and best used for distributed time-ordered<br>IDs.
ULID: 26 characters, generated from timestamp plus randomness, lexicographically sortable,<br>partially decodable because the timestamp is part of the value, and best used for compact sortable<br>public IDs.
Sqids: variable length, generated from numbers, not sortable by creation time by itself,<br>decodable, and best used for public IDs generated from numeric input.
That comparison already tells us something important.
Sqids are generated IDs too, but they are not generated from the same source as UUIDs and ULIDs.
UUIDs and ULIDs generate standalone identifiers from time, randomness, or both. Sqids generate<br>deterministic, reversible IDs from numbers you provide. If your database row has an integer ID of<br>12345, Sqids can turn it into something like NkK9q. But the number is still represented by that<br>string. It is not encryption, it is not hashing, and it is not a security...