A UUID in a URL stops someone changing /orders/1041 to /orders/1042 to see the next record, and it hides how many records you have. It does not stop anyone who already holds the link from sharing it, and it does not replace checking who is allowed to see the page. What you pay for it is a URL nobody can read aloud, an identifier that does not sort, and an index roughly twice the width. Whether that is worth it depends on what the URL guards.
What does a UUID in a URL actually protect?
A sequential id leaks two things. The first is the count: sign up, get user 48213, and you know roughly how big the service is. Look again a week later and you know how fast it is growing. The second is the neighbours: if /invoices/1041 is yours, /invoices/1042 is somebody else's, and trying it costs nothing.
The second problem is only a vulnerability when the server hands the record over without asking whether you may see it. That flaw has a name, insecure direct object reference, and it belongs to the wider family of broken access control. A random identifier does not remove it. It makes it much harder to exploit, because a v4 UUID carries 122 random bits and nobody is guessing the next one. How those bits are laid out in v4 and v7 matters here, because a v7 also tells the reader when the record was created.
So the accurate claim is modest: a random id makes enumeration impractical and hides your size. The fix for the underlying bug is still a permission check on every request.
What does it not protect?
- Anyone who has the link. URLs are pasted into chats, kept in browser history, written to server logs and sent onward in
Refererheaders. If the UUID is the only thing between a stranger and the page, you have built a link that works for whoever holds it, not a login. - Every other place the id shows up. A list endpoint, a sitemap, an export or an email that mentions ten ids has just handed all ten out. Unguessable only helps if nothing else discloses them.
- The creation time, for v7. The leading 48 bits are a millisecond clock that anyone can read.
An unlisted link is a fine choice when "anyone with the link" is the intent, such as a shared document. It is the wrong choice for private data, where the page should check a session whatever the URL looks like.
What does it cost?
- You cannot say it or type it. "Order 1041" survives a phone call. A 36-character string with hyphens does not, and a customer reading one to support will drop a digit.
- It is wider. A UUID is 16 bytes; an integer key is 4 or 8. Every index on the column and every foreign key that points at it repeats those bytes. On a small table that is invisible, and nobody can tell you how much it costs on yours without measuring it.
- A v4 does not sort. Two ids tell you nothing about which record came first. A v7 sorts, at the price of publishing the creation time.
- It is hard to compare by eye. Two log lines that differ in one character out of 36 look identical at a glance.
If you do stay with UUIDs, the UUID generator can drop the hyphens for a 32-character form. That is shorter to copy, but still not something a person can speak, and the hyphenated lowercase form remains the one to store.
When is a sequential id the better answer?
When the record is public anyway. Article numbers, ticket numbers that users quote back to you and invoice numbers gain nothing from being hidden, and they gain a lot from being readable. Many tax regimes also expect invoice numbers to run in sequence without gaps, so check yours before replacing them with random strings.
It also works when access is checked properly on the server, since then a guessed neighbour returns a refusal, not a record. The remaining leak is the count, which only matters if your volume is something you would rather keep private.
What are the middle options?
- Two ids. Keep a small integer as the internal primary key and add a second column holding a random public id. The database keeps its narrow, ordered index, and the URL carries the random one. The cost is an extra unique index and a lookup by public id.
- A short random token. Ten characters from a 32-symbol alphabet is 50 bits. With a million records, a single blind guess hits one about once in a billion tries, which is fine when requests are rate limited and useless when they are not. Base32 is the usual choice of alphabet because it survives being read aloud and typed by hand.
- A readable slug plus an id.
/articles/how-leap-years-work-1041is friendly to people and search engines and protects nothing. A slug generator covers the text part; what makes a good slug covers the rest.
One shortcut to avoid: libraries that encode an integer into a random-looking string. They are reversible obfuscation, not a secret, so treat them as cosmetic.
Can a tool tell me whether my URLs are safe?
No, and it is worth being plain about that. The UUID generator makes v4 or v7 identifiers in your browser and its inspector reads the version and, for a v7, the creation time from the bits. It cannot see your server, so it cannot tell you whether an endpoint checks ownership, whether a list page leaks the ids or whether your random source is sound. That check is a test you run against your own application, logged in as one user and asking for another's record.
If you are weighing a random id against a counter, the UUID generator makes both kinds a thousand at a time so you can see how long the result looks in a real route before you commit. It runs in your browser and nothing you generate is sent anywhere.
And if what you really want is a link that only one person should open, how to send someone a password securely covers the difference between an unguessable link and a properly delivered secret.
Frequently asked questions
Is it safe to put a UUID in a URL?
It is safe from being guessed: a v4 UUID has 122 random bits, so nobody can walk through your records by changing the id. It is not safe as a substitute for a login, because anyone who holds the URL can open it, and URLs leak through logs, history and Referer headers. Check permissions on the server whatever the id looks like.
Should I use sequential ids or UUIDs in URLs?
Use sequential ids when the record is public or people need to quote it, and access is checked on the server anyway. Use random ids when the count of records is something you want private, or when a missing permission check would otherwise expose neighbouring records. Many systems keep both: an integer inside the database and a random public id in the URL.
Does a UUID prevent insecure direct object reference bugs?
No. It makes them much harder to exploit, because an attacker cannot guess another record’s id, but the bug is a missing permission check and it is still there. If a UUID leaks through a list page, an email or a shared screenshot, the record is open to whoever finds it.
Why are UUIDs bad for readable URLs?
A UUID is 36 characters of hex and hyphens that nobody can read aloud, remember or compare at a glance. A customer quoting one over the phone will make a mistake. If people need to refer to the record, give it a short human-facing number or a readable slug alongside the UUID.
How long does a random id in a URL need to be?
Long enough that blind guessing is hopeless given how many guesses you allow. Ten characters from a 32-symbol alphabet is 50 bits, which is about one hit in a billion guesses against a million records. That holds only if you rate limit requests, and it says nothing about who is allowed to view the page.
Last updated September 28, 2026