UUIDs, ULIDs and Sqids: A Practical Deep Dive

shaunpud1 pts0 comments

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...

sqids uuids ulids generated from time

Related Articles