Stop Misusing UUIDs in PHP: How to Choose Between UUID, ULID, and Sqid
This article examines the trade‑offs of UUID, ULID, and Sqid for PHP applications, explaining their designs, storage implications, ordering behavior, security considerations, and provides concrete code examples and a decision‑flow to help developers pick the right identifier for their use case.
UUID, ULID, and Sqid: Practical Deep Dive
Choosing an identifier may seem trivial, but it influences the whole system as it scales.
The Real Problem We Need to Solve
Identifiers serve many scenarios: database primary keys, public route parameters, event IDs, trace IDs, idempotency keys, temporary tokens, and user‑visible reference numbers. Each scenario has different requirements such as compactness, index efficiency, stability, URL safety, ordering, and non‑guessability.
Which ID is best?
The better question is: what characteristics does the system need from an identifier?
Quick Comparison
UUIDv4: 36 characters, random, not time‑ordered, not decodable – best for opaque distributed IDs.
UUIDv7: 36 characters, timestamp + randomness, time‑ordered, partially decodable – best for time‑ordered distributed IDs.
ULID: 26 characters, timestamp + randomness, lexicographically sortable, partially decodable – best for compact, sortable public IDs.
Sqid: variable length, generated from numbers, not time‑ordered, reversible – best for turning integer keys into short public IDs.
Key distinction: Sqid is derived from an existing integer, while UUID/ULID generate independent 128‑bit values.
UUID: The Standard Workhorse
UUID (Universally Unique Identifier) is defined by RFC 9562 (replaces RFC 4122) as a 128‑bit value represented as 36‑character hexadecimal string with hyphens. f81d4fae-7dec-11d0-a765-00a0c91e6bf6 Versions of interest:
UUIDv4: random or pseudo‑random.
UUIDv5: deterministic using SHA‑1 namespace + name.
UUIDv6: time‑reordered for better locality.
UUIDv7: millisecond Unix timestamp + randomness.
Most modern code chooses between UUIDv4 and UUIDv7.
UUIDv4
Generated with ramsey/uuid:
use Ramsey\Uuid\Uuid;
$orderId = Uuid::uuid4()->toString();
echo $orderId; // 8f2d7e76-7f41-4c43-9d67-9d2b4b2c8b98Validation example:
if (!Uuid::isValid($orderId)) {
throw new InvalidArgumentException('Invalid order ID.');
}Advantages: simple, no coordination needed. Suitable for public entity IDs, event IDs, imports, distributed writes, client‑generated IDs, and non‑ordered association IDs.
Drawback: not time‑ordered, which can cause random index placement, page splits, and higher B‑tree overhead when used as a primary key.
UUIDv7
Designed to add time ordering to UUIDs. Structure: 48‑bit millisecond timestamp, version/variant bits, 74 bits of randomness or monotonic counter.
Generation with ramsey/uuid:
use Ramsey\Uuid\Uuid;
$eventId = Uuid::uuid7()->toString();
echo $eventId; // 0194f21a-7d7b-7333-9f4d-1e6f6c7b6b71Provides distributed uniqueness with better locality for append‑heavy workloads. Exposes creation time, which is acceptable for most public resources but may be a concern for time‑sensitive domains.
ULID: Compact and Lexicographically Sortable
ULID (Universally Unique Lexicographically Sortable Identifier) is also 128‑bit, formatted as 26‑character Crockford Base32 string: 48‑bit timestamp (millisecond) + 80‑bit randomness.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
timestamp random
48 bits 80 bitsExample generation with robinvdvleuten/ulid:
use Ulid\Ulid;
$articleId = Ulid::generate();
echo (string) $articleId; // 01JGY7TV4J5XJ6BQWQ9S7F0X9ZRead timestamp:
$articleId = Ulid::generate();
echo $articleId->toTimestamp(); // 1561622862Benefits: shorter than UUID, URL‑safe, case‑insensitive, sortable, still 128‑bit.
ULID Monotonicity
When multiple ULIDs are generated within the same millisecond, the specification increments the random part to preserve order:
01BX5ZZKBKACTAV9WEVGEMMVRZ
01BX5ZZKBKACTAV9WEVGEMMVS0
01BX5ZZKBKACTAV9WEVGEMMVS1This ensures a stable technical ordering without becoming a gap‑less business sequence.
Sqid: IDs Generated from Numbers
Sqid creates short, URL‑safe IDs from one or more non‑negative integers. Example with the official PHP package:
use Sqids\Sqids;
$sqids = new Sqids();
$publicId = $sqids->encode([1, 2, 3]); // 86Rf07
$numbers = $sqids->decode($publicId); // [1, 2, 3]
echo $publicId; // 86Rf07
var_dump($numbers);Useful for exposing a nicer URL instead of /orders/12345 → /orders/NkK9q. Decoding is straightforward, so Sqid is reversible and should not be used for sensitive data.
Canonical check example:
function isCanonicalSqid(Sqids $sqids, string $id): bool {
$numbers = $sqids->decode($id);
if ($numbers === []) return false;
return $sqids->encode($numbers) === $id;
}Sqid supports minimum length and custom alphabets, but these are merely obfuscation, not security.
How They Perform in Databases
Consider three tables:
orders_with_uuid_v4(id CHAR(36) PRIMARY KEY)
orders_with_uuid_v7(id CHAR(36) PRIMARY KEY)
orders_with_integer_id(id BIGINT UNSIGNED PRIMARY KEY)UUIDv4 values are random, causing inserts to scatter across the index, increasing page splits. UUIDv7 and ULID are time‑ordered, usually appending to the index, improving B‑tree locality. Integer IDs are compact and naturally ordered, ideal when distributed generation is unnecessary.
Sqid typically sits above the integer key, translating it to a public ID without changing the primary key.
Storing UUID and ULID
CHAR(36)for standard UUID string. CHAR(26) for ULID string. BINARY(16) for compact 16‑byte storage.
String storage is easy to debug; binary storage saves space but requires consistent conversion.
With ramsey/uuid you can obtain binary bytes via $uuid->getBytes(). For ULID, store the 26‑character string or binary if you handle conversion carefully.
Security and Privacy
Identifiers are not secrets. UUIDv4 is hard to guess but still only an identifier; proper authorization is required. UUIDv7 and ULID expose creation time, which may be sensitive. Sqid is reversible, so never use it for confidential data.
If merely knowing a resource ID grants access, the problem is missing authorization, not the ID format.
Use random tokens (e.g., bin2hex(random_bytes(32))) for secrets instead of IDs.
Public IDs vs Internal IDs
Separate internal integer keys from public identifiers. Example table columns: id – internal integer primary key.
Public UUIDv7/ULID for API exposure.
Or Sqid‑encoded integer for URL presentation.
Practical Decision Process
flowchart TD
A[Need an identifier] --> B{Do you already have an integer ID?}
B -->|Yes| C{Is this only for public presentation?}
C -->|Yes| D[Use Sqids if reversibility is acceptable]
C -->|No| E[Keep the integer internally]
B -->|No| F{Need distributed generation?}
F -->|No| G[An integer key may still be enough]
F -->|Yes| H{Need time ordering?}
H -->|Yes| I[Use UUIDv7 or ULID]
H -->|No| J[Use UUIDv4]The right format depends on requirements, not trends.
Opaque distributed ID, no ordering → UUIDv4.
Standard UUID with better locality → UUIDv7.
Compact, URL‑safe, sortable 128‑bit → ULID.
Existing integer, need short public ID → Sqid.
Need secrecy → random token.
Common Mistakes
Treating Sqid as encryption.
Defaulting to UUIDv4 everywhere despite write‑intensive workloads.
Assuming ULID is secret because it looks random.
Using public IDs without proper authorization checks.
Optimizing storage without measuring impact.
Conclusion
All three identifiers have their place:
UUIDv4 – classic opaque distributed ID.
UUIDv7 – modern choice when time ordering is needed.
ULID – compact, sortable, URL‑safe 128‑bit ID.
Sqid – excellent for generating friendly public IDs from existing integers.
The most important lesson is to understand the required properties—generation before persistence, ordering, exposure, reversibility, and whether the ID protects or merely identifies—before selecting a format.
Hope you enjoyed the article; feel free to share it with friends!
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
