<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: engr anees</title>
    <description>The latest articles on DEV Community by engr anees (@engranees61).</description>
    <link>https://dev.to/engranees61</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3924504%2F157f5790-b026-4a94-80bd-1f9d8a4be52e.png</url>
      <title>DEV Community: engr anees</title>
      <link>https://dev.to/engranees61</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/engranees61"/>
    <language>en</language>
    <item>
      <title>Your JWT decoder might be leaking your tokens. Here's how to check.</title>
      <dc:creator>engr anees</dc:creator>
      <pubDate>Fri, 29 May 2026 18:38:18 +0000</pubDate>
      <link>https://dev.to/engranees61/your-jwt-decoder-might-be-leaking-your-tokens-heres-how-to-check-4ehd</link>
      <guid>https://dev.to/engranees61/your-jwt-decoder-might-be-leaking-your-tokens-heres-how-to-check-4ehd</guid>
      <description>&lt;p&gt;Most developers paste production JWTs into online decoders without thinking. Here's a 10-second DevTools check to see if your token is actually leaving your machine.&lt;/p&gt;

&lt;p&gt;A coworker was debugging an auth bug last month. Standard workflow: copy the JWT from the failing request, paste it into an online decoder, read the payload. I've done it a thousand times. You probably have too.&lt;/p&gt;

&lt;p&gt;Except the token he pasted belonged to a real customer. And the decoder he used is owned by an identity company that's had its share of security incidents.&lt;/p&gt;

&lt;p&gt;Nothing bad happened. &lt;em&gt;Probably.&lt;/em&gt; But it made me think about something I'd never actually checked: &lt;strong&gt;when you paste a JWT into an online decoder, where does that token go?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a JWT actually contains
&lt;/h2&gt;

&lt;p&gt;Quick reminder of why this matters. A JWT isn't encrypted — it's just Base64URL-encoded. Anyone who has the token can read everything in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;header.payload.signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payload routinely contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User ID, email, and role&lt;/li&gt;
&lt;li&gt;Session identifiers&lt;/li&gt;
&lt;li&gt;Token expiry (&lt;code&gt;exp&lt;/code&gt;) and issue time (&lt;code&gt;iat&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Sometimes — against best practice — far more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the part people forget: &lt;strong&gt;a valid, unexpired JWT is a live credential.&lt;/strong&gt; If it hasn't expired, whoever holds it can often impersonate the user. Pasting it into a random website is functionally similar to pasting a password.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 10-second check
&lt;/h2&gt;

&lt;p&gt;Most online JWT decoders claim to be "secure" and "client-side." Some are. Some aren't. You don't have to trust the claim — you can verify it yourself in 10 seconds:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the decoder in your browser&lt;/li&gt;
&lt;li&gt;Open DevTools → &lt;strong&gt;Network&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Clear the network log&lt;/li&gt;
&lt;li&gt;Paste a JWT and decode it&lt;/li&gt;
&lt;li&gt;Watch the Network tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;If any request fires when you decode — your token left your machine.&lt;/strong&gt; A truly client-side decoder fires &lt;em&gt;zero&lt;/em&gt; network requests during decoding. The JavaScript does everything locally; nothing is sent anywhere.&lt;/p&gt;

&lt;p&gt;Try this on whatever decoder you currently use. You might be surprised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why most "online" tools send data
&lt;/h2&gt;

&lt;p&gt;It's usually not malicious. Building decoding logic on the server is sometimes just easier, or the tool wants analytics on what's being decoded, or there are ads loading third-party scripts onto the same page where your token is now sitting in the DOM. Even if the tool itself is trustworthy, every third-party script on that page can read what's on it.&lt;/p&gt;

&lt;p&gt;For a throwaway test token, who cares. For a production token with a real user's session — that's the difference between a habit you should keep and one you should drop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: decode locally
&lt;/h2&gt;

&lt;p&gt;Two good options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Decode it yourself.&lt;/strong&gt; It's genuinely trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decodeJWT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note: real JWTs use Base64URL, so for tokens with &lt;code&gt;-&lt;/code&gt; or &lt;code&gt;_&lt;/code&gt; you'll want to convert those before &lt;code&gt;atob&lt;/code&gt;. But this is the gist.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Use a decoder that's verifiably browser-only.&lt;/strong&gt; I ended up building &lt;a href="https://freedevtool.org/jwt-decoder" rel="noopener noreferrer"&gt;a JWT decoder&lt;/a&gt; that runs entirely in the browser — no signup, no ads, no server. Run the DevTools check on it and you'll see zero requests fire when you decode. That was the whole point: I wanted a decoder I could paste a production token into without thinking twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Pasting tokens into online tools is muscle memory for most of us. That's fine — until the token is real.&lt;/p&gt;

&lt;p&gt;Next time you reach for an online decoder, spend 10 seconds in the Network tab first. If the tool sends your token anywhere, find one that doesn't. Your future incident-response self will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your JWT debugging workflow? Do you decode locally or trust an online tool? Curious what others do.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>jwt</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Stop using UUID v4 for database primary keys — UUIDv7 is the 2026 default</title>
      <dc:creator>engr anees</dc:creator>
      <pubDate>Tue, 12 May 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/engranees61/stop-using-uuid-v4-for-database-primary-keys-uuidv7-is-the-2026-default-2f4b</link>
      <guid>https://dev.to/engranees61/stop-using-uuid-v4-for-database-primary-keys-uuidv7-is-the-2026-default-2f4b</guid>
      <description>&lt;p&gt;Your B-tree indexes hate UUID v4. They've hated it for years. You probably haven't noticed because the slowdown is gradual — until your table hits 10 million rows and inserts start taking 200ms instead of 5ms.&lt;/p&gt;

&lt;p&gt;UUIDv7 fixes this. RFC 9562 made it official in May 2024. PostgreSQL 18 ships native support. MySQL 8.4 has a helper. Most ORM and language libraries added v7 in 2024–2025. By 2026, &lt;strong&gt;v4 should be the exception, not the default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This post is the "why", the "when", and the "how to migrate without breaking things".&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with v4
&lt;/h2&gt;

&lt;p&gt;A v4 UUID is 122 bits of pure randomness:&lt;/p&gt;

&lt;p&gt;e7d1a8c4-9fe5-4a32-8c7e-3f9d2e8a1c4b&lt;/p&gt;

&lt;p&gt;Looks fine. Random is what we want, right? Yes — for things that need to be &lt;strong&gt;unguessable&lt;/strong&gt;. No — for things that go into a B-tree index.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why B-trees hate randomness
&lt;/h3&gt;

&lt;p&gt;Database indexes (B-tree, which is what Postgres, MySQL, SQLite, MSSQL all use for primary keys by default) work best when new entries arrive &lt;strong&gt;in roughly sorted order&lt;/strong&gt;. The tree can append to the rightmost leaf page, keeping the structure compact and writes localized.&lt;/p&gt;

&lt;p&gt;When you insert 1 million v4 UUIDs, each one lands in a &lt;strong&gt;random page&lt;/strong&gt; of the index. The result:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Random page splits as the tree rebalances&lt;/li&gt;
&lt;li&gt;Buffer cache thrashing — random pages keep getting evicted&lt;/li&gt;
&lt;li&gt;Increased WAL/redo log volume — random pages mean random disk writes&lt;/li&gt;
&lt;li&gt;Eventually: index fragmentation, slower queries, larger storage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Public benchmarks on InnoDB (MySQL 8.0) at 10 million rows show v4 UUID inserts roughly 3× slower than auto-increment integer keys, with index size around 40% larger. The exact numbers vary by database, hardware, and workload, but the pattern is universal: &lt;strong&gt;random keys destroy B-tree performance at scale&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  "But I'm not at scale yet"
&lt;/h3&gt;

&lt;p&gt;True. At 100K rows, nobody notices. At 1M rows, you might notice slow inserts on bulk operations. At 10M rows, your DBA writes you a long Slack message.&lt;/p&gt;

&lt;p&gt;The cost of using v7 from day one: zero. Same UUID format, same byte length, same tooling.&lt;/p&gt;

&lt;p&gt;The cost of migrating from v4 to v7 at 100M rows: you don't migrate. You live with it forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  What UUIDv7 Actually Is
&lt;/h2&gt;

&lt;p&gt;UUIDv7 keeps the same &lt;code&gt;xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx&lt;/code&gt; format as v4 but rearranges the bytes:&lt;/p&gt;

&lt;p&gt;0190a4b3-1c2d-7e85-a734-2c8e9f1d4a5b&lt;br&gt;
└────timestamp────┘ ││ │└──random──┘&lt;br&gt;
│└── version (7)&lt;br&gt;
└─── variant&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;First 48 bits&lt;/strong&gt; (&lt;code&gt;0190a4b3-1c2d&lt;/code&gt;) — Unix milliseconds timestamp&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next 4 bits&lt;/strong&gt; — Version marker (always &lt;code&gt;7&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next 12 bits&lt;/strong&gt; — Sub-millisecond ordering or random&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next 2 bits&lt;/strong&gt; — Variant marker&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Last 62 bits&lt;/strong&gt; — Pure random&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight: the &lt;strong&gt;timestamp is in the most significant bits&lt;/strong&gt;, so v7 UUIDs sort chronologically by string OR byte comparison.&lt;/p&gt;

&lt;p&gt;Insert 1 million v7 UUIDs sequentially, and the B-tree is happy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All inserts land near the rightmost page&lt;/li&gt;
&lt;li&gt;Buffer cache stays warm&lt;/li&gt;
&lt;li&gt;WAL volume stays low (sequential writes)&lt;/li&gt;
&lt;li&gt;Index size matches ordered-key efficiency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same uniqueness guarantee as v4 (74 bits of randomness — collision probability still effectively zero at realistic scale). Same length, same character set, same APIs. &lt;strong&gt;Just better-shaped randomness.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  When to Use v7 vs v4
&lt;/h2&gt;

&lt;p&gt;This is the entire decision tree:&lt;/p&gt;

&lt;p&gt;Is the UUID stored in a B-tree index?&lt;br&gt;
(database PK, MongoDB _id, any indexed FK)&lt;br&gt;
│&lt;br&gt;
├── YES → use v7&lt;br&gt;
│ Examples: user_id, order_id, post_id&lt;br&gt;
│&lt;br&gt;
└── NO → does the UUID need to NOT leak time information?&lt;br&gt;
│&lt;br&gt;
├── YES → use v4&lt;br&gt;
│ Examples: password reset tokens, share-by-URL&lt;br&gt;
│ secrets, anti-CSRF tokens, webhook signatures&lt;br&gt;
│&lt;br&gt;
└── NO → use v7 anyway (slightly cheaper to generate)&lt;/p&gt;

&lt;p&gt;That's it. v7 is the default; v4 is the security exception.&lt;/p&gt;
&lt;h3&gt;
  
  
  "But v7 leaks the creation timestamp!"
&lt;/h3&gt;

&lt;p&gt;Yes — the leading 48 bits are a millisecond timestamp. Anyone who sees a v7 UUID can read off when it was generated.&lt;/p&gt;

&lt;p&gt;For a database primary key, this is &lt;strong&gt;fine&lt;/strong&gt; (rows usually have a &lt;code&gt;created_at&lt;/code&gt; column anyway). For a sharing token where guessability of "when this was created" enables an attack, v4 stays correct.&lt;/p&gt;

&lt;p&gt;Don't use v7 for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Password reset tokens (timestamp narrows brute-force window)&lt;/li&gt;
&lt;li&gt;Email verification links (same)&lt;/li&gt;
&lt;li&gt;Anti-CSRF tokens&lt;/li&gt;
&lt;li&gt;Webhook signatures&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For everything else: v7.&lt;/p&gt;


&lt;h2&gt;
  
  
  Generating UUIDv7 in 2026
&lt;/h2&gt;
&lt;h3&gt;
  
  
  JavaScript / Node.js (uuid v10+)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;v7&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuidv7&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv7&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// '0190a4b3-1c2d-7e85-a734-2c8e9f1d4a5b'&lt;/span&gt;

&lt;span class="nx"&gt;Python&lt;/span&gt; &lt;span class="mf"&gt;3.13&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdlib&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;

&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;Go &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;github.com/google/uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NewV7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;Rust &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;now_v7&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;PostgreSQL&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;native&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;extension&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;uuidv7&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0190a4b3-1c2d-7e85-a734-2c8e9f1d4a5b&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;TABLE&lt;/span&gt; &lt;span class="nf"&gt;users &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="nx"&gt;UUID&lt;/span&gt; &lt;span class="nx"&gt;PRIMARY&lt;/span&gt; &lt;span class="nx"&gt;KEY&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="nf"&gt;uuidv7&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="nx"&gt;TEXT&lt;/span&gt; &lt;span class="nx"&gt;NOT&lt;/span&gt; &lt;span class="nx"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;created_at&lt;/span&gt; &lt;span class="nx"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;MySQL&lt;/span&gt; &lt;span class="mf"&gt;8.4&lt;/span&gt;
&lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;native&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;as&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;mid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Generate&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="nx"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;stored&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nx"&gt;DELIMITER&lt;/span&gt; &lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="nx"&gt;CREATE&lt;/span&gt; &lt;span class="nx"&gt;FUNCTION&lt;/span&gt; &lt;span class="nf"&gt;uuid_v7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;RETURNS&lt;/span&gt; &lt;span class="nc"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;BEGIN&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;byte&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="nx"&gt;random&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="nx"&gt;bits&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;Implementation&lt;/span&gt; &lt;span class="nx"&gt;omitted&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;brevity&lt;/span&gt;
  &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nx"&gt;See&lt;/span&gt; &lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;MichaelDimmitt&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;uuidv7&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;mysql&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;working&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;
&lt;span class="nx"&gt;END&lt;/span&gt; &lt;span class="c1"&gt;//&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;No install — just need one quickly&lt;/strong&gt;&lt;br&gt;
Generate one in your browser: &lt;a href="https://freedevtool.org/uuid-generator" rel="noopener noreferrer"&gt;UUIDv7 Generator&lt;/a&gt;. Supports v1, v4, v5, and v7. All client-side, no upload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating an Existing v4 Table&lt;/strong&gt;&lt;br&gt;
Short answer: don't, unless you have measured proof of pain.&lt;/p&gt;

&lt;p&gt;Migrating primary keys is one of the most expensive database operations you can do — every foreign key, every join, every index gets rebuilt.&lt;/p&gt;

&lt;p&gt;Pick the strategy that matches your real constraint:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: New tables only (recommended)&lt;/strong&gt;&lt;br&gt;
Use v7 for new tables going forward. Leave v4 tables alone. After a few years, v7 tables dominate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2: Add a v7 secondary key&lt;/strong&gt;&lt;br&gt;
Keep v4 as the primary key. Add a secondary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="nv"&gt;`ALTER TABLE users
  ADD COLUMN public_id UUID DEFAULT uuidv7() UNIQUE;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use v4 internally, v7 externally for new query patterns that need range scans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3: Full migration (only if performance is on fire)&lt;/strong&gt;&lt;br&gt;
This is a 6–12 month project on a real application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add id_v7 UUID column to all tables
Backfill v7 IDs in batches (timestamps reflecting created_at)
Update all foreign keys to point at id_v7
Switch the primary key — the painful part
Drop the old v4 column
Don't do this without benchmarks proving v4 fragmentation is the actual bottleneck. Often the real culprit is missing indexes or N+1 queries.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other Ordered ID Formats (and Why v7 Won)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;u&gt;Format Year    2026 status&lt;/u&gt;&lt;/li&gt;
&lt;li&gt;UUIDv7    2024 (RFC 9562) Standard, native in Postgres 18, libraries everywhere&lt;/li&gt;
&lt;li&gt;ULID  2016    Still common; v7 is the spec'd successor&lt;/li&gt;
&lt;li&gt;CUID2 2022    Niche; works if already using&lt;/li&gt;
&lt;li&gt;NanoID    2017    Different use case (short URL slugs)&lt;/li&gt;
&lt;li&gt;Twitter Snowflake 2010    Specific to high-throughput distributed systems&lt;/li&gt;
&lt;li&gt;MongoDB ObjectID  2009    MongoDB-specific&lt;/li&gt;
&lt;li&gt;Starting a project in 2026: v7. Already using ULID and it works: keep it. Using v4 because that's what crypto.randomUUID() returns: switch the import.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TL;DR&lt;br&gt;
B-tree indexes hate randomness. v4 UUIDs cause page splits, cache thrashing, and bloat as tables grow.&lt;br&gt;
UUIDv7 fixes this by putting a millisecond timestamp in the leading 48 bits, so inserts land in monotonic order.&lt;br&gt;
Same format, same uniqueness guarantee — drop-in replacement for new tables.&lt;br&gt;
Use v4 only when timestamp leakage is a security issue (password reset, anti-CSRF, share-by-URL secrets).&lt;br&gt;
Don't migrate existing v4 tables unless you have measured proof of fragmentation pain.&lt;br&gt;
Postgres 18, MySQL 8.4, all major UUID libraries support v7 natively or via a one-line update.&lt;br&gt;
What's your team's default in 2026 — still v4, switched to v7, or something else (ULID, Snowflake)? Curious to hear the migration stories in comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>postgres</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
