<?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: William Andrews</title>
    <description>The latest articles on DEV Community by William Andrews (@willivan0706).</description>
    <link>https://dev.to/willivan0706</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%2F3837567%2Fb8f6f56d-693b-4bfb-ad3c-42871995be69.png</url>
      <title>DEV Community: William Andrews</title>
      <link>https://dev.to/willivan0706</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/willivan0706"/>
    <language>en</language>
    <item>
      <title>How to decode and debug a JWT without installing anything</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Sat, 02 May 2026 04:59:58 +0000</pubDate>
      <link>https://dev.to/willivan0706/how-to-decode-and-debug-a-jwt-without-installing-anything-5gi1</link>
      <guid>https://dev.to/willivan0706/how-to-decode-and-debug-a-jwt-without-installing-anything-5gi1</guid>
      <description>&lt;p&gt;You're staring at a string that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Something in your auth flow is broken. The API is returning 401s, the user session isn't persisting, or someone handed you a token and asked why it isn't working. You need to see what's inside it — right now, without installing a library or setting up a project.&lt;/p&gt;

&lt;p&gt;This guide shows you how to decode any JWT instantly in the browser, what every part of the token means, and how to diagnose the most common JWT errors from the decoded contents alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  The anatomy of a JWT
&lt;/h2&gt;

&lt;p&gt;A JWT is three Base64URL-encoded strings separated by dots. There's no encryption happening at the decoding stage — the payload is readable by anyone who has the token. The signature at the end is what makes tampering detectable, but decoding the contents requires no secret key.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  The header
&lt;/h3&gt;

&lt;p&gt;The header tells you which algorithm was used to sign the token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HS256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"typ"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JWT"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common algorithm values: &lt;code&gt;HS256&lt;/code&gt; (HMAC-SHA256, symmetric), &lt;code&gt;RS256&lt;/code&gt; (RSA-SHA256, asymmetric), &lt;code&gt;ES256&lt;/code&gt; (ECDSA). If you see &lt;code&gt;"alg": "none"&lt;/code&gt; — that's a serious red flag. It means the token has no signature and should be rejected by any properly configured server.&lt;/p&gt;

&lt;h3&gt;
  
  
  The payload
&lt;/h3&gt;

&lt;p&gt;The payload contains claims — key-value pairs that assert things about the user or session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"john@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1516239022&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1516242622&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://auth.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The signature
&lt;/h3&gt;

&lt;p&gt;The signature is a hash of the header and payload, signed with the server's secret or private key. You cannot verify it without that key — but you don't need to verify it to read the payload. Decoding and verifying are two separate operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Standard claims and what they mean
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;sub&lt;/code&gt; (Subject)&lt;/strong&gt; — the principal this token is about, typically a user ID. This is what your backend uses to identify who the token belongs to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iss&lt;/code&gt; (Issuer)&lt;/strong&gt; — who created and signed the token. Often a domain or auth service URL. Your server should validate that this matches the expected issuer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;aud&lt;/code&gt; (Audience)&lt;/strong&gt; — who the token is intended for. A token issued for &lt;code&gt;api.example.com&lt;/code&gt; should be rejected by &lt;code&gt;other-api.example.com&lt;/code&gt;. Mismatched audience is a common source of 401 errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;exp&lt;/code&gt; (Expiration)&lt;/strong&gt; — a Unix timestamp after which the token must be rejected. This is the most common reason for 401 errors in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iat&lt;/code&gt; (Issued At)&lt;/strong&gt; — when the token was created, as a Unix timestamp. Useful for calculating how old the token is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;nbf&lt;/code&gt; (Not Before)&lt;/strong&gt; — the token must be rejected before this time. Less common, used when tokens are issued in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;jti&lt;/code&gt; (JWT ID)&lt;/strong&gt; — a unique identifier for this specific token, used to prevent replay attacks.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to decode a JWT in the browser right now
&lt;/h2&gt;

&lt;p&gt;Paste the token into &lt;a href="https://devcrate.net/jwt/" rel="noopener noreferrer"&gt;DevCrate's JWT Debugger&lt;/a&gt; and you'll see the header and payload decoded instantly — no account, no install, nothing sent to a server.&lt;/p&gt;

&lt;p&gt;If you prefer to do it in code:&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="nx"&gt;parts&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid JWT format&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;decode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;base64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&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="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;padded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;4&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="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;padded&lt;/span&gt;&lt;span class="p"&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="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// expiry timestamp&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// user ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note what this does and doesn't do: it reads the payload without verifying the signature. Fine for debugging — never use this in place of server-side verification for actual auth decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Diagnosing common JWT errors from the decoded payload
&lt;/h2&gt;

&lt;h3&gt;
  
  
  401 — "Token expired"
&lt;/h3&gt;

&lt;p&gt;Check the &lt;code&gt;exp&lt;/code&gt; claim. Convert it to a readable date:&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="nx"&gt;exp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// → "4/30/2026, 11:42:00 PM"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that date is in the past, the token is expired. The fix is on the client — it needs to refresh the token before it expires or request a new one after receiving a 401.&lt;/p&gt;

&lt;h3&gt;
  
  
  401 — "Invalid audience"
&lt;/h3&gt;

&lt;p&gt;Check the &lt;code&gt;aud&lt;/code&gt; claim. Common mismatches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token issued for &lt;code&gt;https://api.example.com&lt;/code&gt;, server expects &lt;code&gt;api.example.com&lt;/code&gt; (no scheme)&lt;/li&gt;
&lt;li&gt;Token issued for staging, hitting production&lt;/li&gt;
&lt;li&gt;Token issued for one service being used against a different service&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aud&lt;/code&gt; is an array and the server is checking for a single string match&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  401 — "Invalid issuer"
&lt;/h3&gt;

&lt;p&gt;Check the &lt;code&gt;iss&lt;/code&gt; claim. Same class of problem as audience mismatch. Common in multi-environment setups where a dev token gets used against a prod server, or when an auth provider URL changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  403 — "Insufficient permissions"
&lt;/h3&gt;

&lt;p&gt;The token is valid but the user doesn't have access. Look for custom claims in the payload — &lt;code&gt;role&lt;/code&gt;, &lt;code&gt;permissions&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;groups&lt;/code&gt;. These are application-specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"viewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid profile"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token present but user isn't authenticated
&lt;/h3&gt;

&lt;p&gt;Check whether the token is being sent correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common mistakes: "Bearer" misspelled or missing, double space between Bearer and the token, or the token has been URL-encoded in transit (look for &lt;code&gt;%2B&lt;/code&gt; or &lt;code&gt;%3D&lt;/code&gt; in the token string).&lt;/p&gt;

&lt;h3&gt;
  
  
  Token looks valid but server rejects it
&lt;/h3&gt;

&lt;p&gt;If the header and payload decode cleanly and all claims look correct, the problem is the signature. Possible causes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token signed with a different secret than the server is using to verify&lt;/li&gt;
&lt;li&gt;Token was modified after signing&lt;/li&gt;
&lt;li&gt;Algorithm mismatch — &lt;code&gt;HS256&lt;/code&gt; vs &lt;code&gt;RS256&lt;/code&gt; when switching auth libraries&lt;/li&gt;
&lt;li&gt;Clock skew — server's system time is off enough that &lt;code&gt;exp&lt;/code&gt; and &lt;code&gt;nbf&lt;/code&gt; checks fail&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Reading tokens from real-world locations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From browser storage:&lt;/strong&gt; DevTools → Application → Local Storage or Session Storage → look for keys like &lt;code&gt;token&lt;/code&gt;, &lt;code&gt;access_token&lt;/code&gt;, &lt;code&gt;auth_token&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From browser cookies:&lt;/strong&gt; DevTools → Application → Cookies → look for anything with a value starting with &lt;code&gt;ey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From a network request:&lt;/strong&gt; DevTools → Network → click a failing request → Headers tab → find the &lt;code&gt;Authorization&lt;/code&gt; header → copy the value after "Bearer ".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From curl:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.example.com/login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email":"user@example.com","password":"secret"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.access_token'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  One thing to never do with a JWT debugger
&lt;/h2&gt;

&lt;p&gt;Never paste a production JWT containing real user data into a third-party website. Most online JWT tools send the token to their servers — your token contains claims about real users, and a valid token can be used to make authenticated API calls.&lt;/p&gt;

&lt;p&gt;DevCrate's JWT Debugger decodes entirely in the browser using JavaScript — the token never leaves your machine. You can verify this by opening DevTools → Network while using the tool and confirming no requests are made when you paste a token.&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>URL encoding — what it is, when it breaks, and how to fix it</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Tue, 28 Apr 2026 00:23:17 +0000</pubDate>
      <link>https://dev.to/willivan0706/url-encoding-what-it-is-when-it-breaks-and-how-to-fix-it-13i2</link>
      <guid>https://dev.to/willivan0706/url-encoding-what-it-is-when-it-breaks-and-how-to-fix-it-13i2</guid>
      <description>&lt;p&gt;URL encoding bugs are some of the most frustrating to debug because they're invisible. A string looks fine in your code, but by the time it arrives at the server it's been mangled — spaces became &lt;code&gt;+&lt;/code&gt; signs, the &lt;code&gt;&amp;amp;&lt;/code&gt; that was part of your data got interpreted as a query string separator, or the whole parameter silently disappeared.&lt;/p&gt;

&lt;p&gt;This guide covers how URL encoding works, where it goes wrong, and the exact functions to use in JavaScript and other languages to handle it correctly every time.&lt;/p&gt;




&lt;h2&gt;
  
  
  What URL encoding actually is
&lt;/h2&gt;

&lt;p&gt;URLs can only contain a specific set of characters safely: letters (A–Z, a–z), digits (0–9), and a handful of special characters (&lt;code&gt;-&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;). Everything else — spaces, ampersands, equals signs, slashes, non-ASCII characters — needs to be encoded before it can be included in a URL without breaking its structure.&lt;/p&gt;

&lt;p&gt;The encoding scheme is called &lt;strong&gt;percent-encoding&lt;/strong&gt;. Each unsafe character is replaced by a percent sign followed by its two-digit hexadecimal ASCII code. A space becomes &lt;code&gt;%20&lt;/code&gt;, an ampersand becomes &lt;code&gt;%26&lt;/code&gt;, an equals sign becomes &lt;code&gt;%3D&lt;/code&gt;, a forward slash becomes &lt;code&gt;%2F&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello World   →   Hello%20World
user@example  →   user%40example
price=5&amp;amp;qty=2 →   price%3D5%26qty%3D2
café          →   caf%C3%A9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Non-ASCII characters like accented letters and emoji are first encoded to UTF-8 bytes, then each byte is percent-encoded. That's why &lt;code&gt;café&lt;/code&gt; becomes &lt;code&gt;caf%C3%A9&lt;/code&gt; — the é character is two bytes in UTF-8 (&lt;code&gt;0xC3&lt;/code&gt;, &lt;code&gt;0xA9&lt;/code&gt;), each percent-encoded.&lt;/p&gt;




&lt;h2&gt;
  
  
  The two contexts that confuse everyone
&lt;/h2&gt;

&lt;p&gt;URL encoding isn't one thing — it's two different operations applied in two different places, and mixing them up is the root cause of most encoding bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encoding a full URL
&lt;/h3&gt;

&lt;p&gt;When you have a complete URL and want to make it safe to use as a link or pass to a browser, you want to encode only the characters that are illegal in URLs entirely — but leave the structural characters (&lt;code&gt;:&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;=&lt;/code&gt;) alone, because those are part of the URL's structure.&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="nf"&gt;encodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search?q=hello world&amp;amp;lang=en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → "https://example.com/search?q=hello%20world&amp;amp;lang=en"&lt;/span&gt;
&lt;span class="c1"&gt;//   Note: &amp;amp; and = are NOT encoded — they're structural&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encoding a query parameter value
&lt;/h3&gt;

&lt;p&gt;When you're inserting a value into a URL — a search term, a redirect URL, a user-submitted string — you need to encode everything that isn't a plain alphanumeric character, including the structural characters. If your value contains an &lt;code&gt;&amp;amp;&lt;/code&gt; and you don't encode it, the browser will interpret it as a parameter separator and split your value in two.&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="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world &amp;amp; more&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → "hello%20world%20%26%20more"&lt;/span&gt;
&lt;span class="c1"&gt;//   Note: &amp;amp; IS encoded as %26 — it's data, not structure&lt;/span&gt;

&lt;span class="c1"&gt;// Building a URL with a parameter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price &amp;lt; 100 &amp;amp; in stock&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://example.com/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// → "https://example.com/search?q=price%20%3C%20100%20%26%20in%20stock"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; use &lt;code&gt;encodeURI()&lt;/code&gt; on complete URLs. Use &lt;code&gt;encodeURIComponent()&lt;/code&gt; on individual values being inserted into URLs. Use &lt;code&gt;encodeURIComponent()&lt;/code&gt; far more often than &lt;code&gt;encodeURI()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The space problem: %20 vs +
&lt;/h2&gt;

&lt;p&gt;Spaces are the single most common source of URL encoding confusion because there are two valid encodings for them, used in different contexts:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;%20&lt;/code&gt; is the standard percent-encoding for a space and works correctly in any part of a URL.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;+&lt;/code&gt; represents a space only in the query string of &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; content — the format HTML forms use when submitted. In a URL path, &lt;code&gt;+&lt;/code&gt; is a literal plus sign, not a space.&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="c1"&gt;// These are equivalent in a query string:&lt;/span&gt;
&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.com/search?q=hello+world&lt;/span&gt;
&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.com/search?q=hello%20world&lt;/span&gt;

&lt;span class="c1"&gt;// But in a path, + is literal:&lt;/span&gt;
&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.com/files/hello+world.txt   // file named "hello+world.txt"&lt;/span&gt;
&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//example.com/files/hello%20world.txt // file named "hello world.txt"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The safe rule:&lt;/strong&gt; always use &lt;code&gt;%20&lt;/code&gt; for spaces unless you're specifically working with HTML form submissions. Never rely on &lt;code&gt;+&lt;/code&gt; outside of form data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Double-encoding — the silent killer
&lt;/h2&gt;

&lt;p&gt;Double-encoding happens when you encode something that's already encoded. The result looks almost right but is subtly broken:&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="c1"&gt;// Original string&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Encoded once (correct)&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello%20world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Encoded twice (broken — the % itself gets encoded)&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello%2520world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;//       ↑ %25 is the encoding for %, so %20 became %2520&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server receives &lt;code&gt;hello%2520world&lt;/code&gt; and decodes it once, it gets &lt;code&gt;hello%20world&lt;/code&gt; — a string containing a literal percent sign, two, zero. Not the space you intended.&lt;/p&gt;

&lt;p&gt;Double-encoding happens most often when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You encode a value, then pass it through a function that encodes again&lt;/li&gt;
&lt;li&gt;You build a URL from already-encoded parts and then encode the whole URL&lt;/li&gt;
&lt;li&gt;A framework encodes parameters automatically and you've also encoded them manually&lt;/li&gt;
&lt;li&gt;You're constructing a redirect URL where the target URL is itself a query parameter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is to decode first if you're unsure whether something is already encoded:&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;safeEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;h2&gt;
  
  
  Characters that have special meaning
&lt;/h2&gt;

&lt;p&gt;Reserved — must be encoded when used as data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:  %3A    /  %2F    ?  %3F    #  %23
[  %5B    ]  %5D    @  %40    !  %21
$  %24    &amp;amp;  %26    '  %27    (  %28
)  %29    *  %2A    +  %2B    ,  %2C
;  %3B    =  %3D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unreserved — never need encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A–Z   a–z   0–9   -   _   .   ~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;encodeURIComponent()&lt;/code&gt; does not encode: &lt;code&gt;A–Z a–z 0–9 - _ . ! ~ * ' ( )&lt;/code&gt;. If you need those encoded too (e.g. in an OAuth signature), you'll need to add them manually after encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decoding URL-encoded strings
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello%20world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;// → "hello world"&lt;/span&gt;
&lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;caf%C3%A9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;// → "café"&lt;/span&gt;
&lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price%3D5%26qty%3D2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// → "price=5&amp;amp;qty=2"&lt;/span&gt;

&lt;span class="c1"&gt;// Handle malformed input safely&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;str&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;h2&gt;
  
  
  URL encoding in other languages
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quote_plus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unquote_plus&lt;/span&gt;

&lt;span class="nf"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# → "hello%20world"
&lt;/span&gt;&lt;span class="nf"&gt;quote_plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# → "hello+world"  (form encoding)
&lt;/span&gt;&lt;span class="nf"&gt;unquote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello%20world&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# → "hello world"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PHP&lt;/span&gt;
&lt;span class="nb"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// → "hello+world"  (form encoding)&lt;/span&gt;
&lt;span class="nb"&gt;rawurlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// → "hello%20world" (RFC 3986)&lt;/span&gt;
&lt;span class="c1"&gt;// Use rawurlencode() for URL paths and components&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"net/url"&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c"&gt;// → "hello+world"&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathEscape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello world"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c"&gt;// → "hello%20world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Encoding a redirect URL as a parameter
&lt;/h2&gt;

&lt;p&gt;One of the trickiest real-world cases — a redirect URL that is itself a query parameter:&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="c1"&gt;// Wrong — the inner ? and &amp;amp; break the outer URL structure&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/dashboard?tab=settings&amp;amp;view=list&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/login?next=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// The server sees next=https://example.com/dashboard?tab=settings&lt;/span&gt;
&lt;span class="c1"&gt;// and &amp;amp;view=list as a separate parameter&lt;/span&gt;

&lt;span class="c1"&gt;// Correct — encode the entire redirect URL as a value&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`/login?next=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// → /login?next=https%3A%2F%2Fexample.com%2Fdashboard%3Ftab%3Dsettings%26view%3Dlist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Query string construction the right way
&lt;/h2&gt;

&lt;p&gt;Instead of manually encoding and concatenating — which is error-prone — use built-in tools:&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="c1"&gt;// URLSearchParams handles encoding automatically&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world &amp;amp; more&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// → "q=hello+world+%26+more&amp;amp;lang=en&amp;amp;page=1"&lt;/span&gt;

&lt;span class="c1"&gt;// Append to a URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello world &amp;amp; more&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lang&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// → "https://example.com/search?q=hello+world+%26+more&amp;amp;lang=en"&lt;/span&gt;

&lt;span class="c1"&gt;// Read parameters safely — already decoded for you&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;searchParams&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common encoding bugs and their fixes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Parameter value contains &amp;amp; and gets split&lt;/strong&gt; — you're not encoding the value before inserting it. Use &lt;code&gt;encodeURIComponent()&lt;/code&gt; on the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spaces arrive at the server as literal +&lt;/strong&gt; — you're using form encoding (&lt;code&gt;+&lt;/code&gt;) in a context that expects percent-encoding. Switch to &lt;code&gt;encodeURIComponent()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;%25 appears where %20 should be&lt;/strong&gt; — double-encoding. Find where you're encoding already-encoded data and remove the redundant step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-ASCII characters arrive garbled&lt;/strong&gt; — the string isn't being encoded to UTF-8 before percent-encoding. In JavaScript, &lt;code&gt;encodeURIComponent()&lt;/code&gt; always uses UTF-8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A parameter value is empty on the server&lt;/strong&gt; — the value contains characters that terminate the parameter without encoding. Encode the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL works in the browser but fails in fetch or curl&lt;/strong&gt; — browsers are lenient and will often fix malformed URLs automatically. HTTP clients won't. Always encode explicitly when constructing URLs in code.&lt;/p&gt;




&lt;p&gt;If you need to quickly encode or decode a URL or string, &lt;a href="https://devcrate.net/url/" rel="noopener noreferrer"&gt;DevCrate's URL Encoder/Decoder&lt;/a&gt; runs entirely in your browser — nothing you paste is sent anywhere.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SQL formatting — a practical guide for developers</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Mon, 20 Apr 2026 01:35:27 +0000</pubDate>
      <link>https://dev.to/willivan0706/sql-formatting-a-practical-guide-for-developers-4b66</link>
      <guid>https://dev.to/willivan0706/sql-formatting-a-practical-guide-for-developers-4b66</guid>
      <description>&lt;p&gt;SQL is one of the oldest languages developers still write by hand every day. It's also one of the most inconsistently formatted. Open any codebase with more than one contributor and you'll find queries written in four different styles — keywords uppercase in one file, lowercase in another, indentation that made sense to whoever wrote it six months ago but nobody else.&lt;/p&gt;

&lt;p&gt;This guide covers the conventions that make SQL readable, why they exist, and how to apply them consistently regardless of which database you're using.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why formatting matters more in SQL than most languages
&lt;/h2&gt;

&lt;p&gt;Most languages have autoformatters that make style debates irrelevant — Prettier for JavaScript, Black for Python, gofmt for Go. SQL has no universal equivalent. A query can be written in one line or twenty, with keywords uppercase or lowercase, with or without aliases, and the database will execute it identically. The only thing formatting affects is how easy the query is to read, debug, and modify.&lt;/p&gt;

&lt;p&gt;That gap matters more for SQL than most languages because SQL queries tend to be long, because they often live in places autoformatters don't reach (migration files, ORM string literals, analytics dashboards, stored procedures), and because a poorly formatted query in a production codebase is genuinely difficult to audit for correctness.&lt;/p&gt;

&lt;p&gt;A well-formatted query tells the reader what it's doing before they have to parse it. A poorly formatted one makes them do the parser's job manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  The core conventions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Uppercase keywords
&lt;/h3&gt;

&lt;p&gt;Capitalize SQL reserved words: &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, &lt;code&gt;ON&lt;/code&gt;, &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;LIMIT&lt;/code&gt;, &lt;code&gt;AS&lt;/code&gt;, &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt;, &lt;code&gt;IN&lt;/code&gt;, &lt;code&gt;IS&lt;/code&gt;, &lt;code&gt;NULL&lt;/code&gt;, &lt;code&gt;LIKE&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;, &lt;code&gt;CASE&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt;, &lt;code&gt;THEN&lt;/code&gt;, &lt;code&gt;ELSE&lt;/code&gt;, &lt;code&gt;END&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lowercase everything else: table names, column names, aliases, string literals, function names. This distinction makes keywords visually separate from data — it immediately tells the reader which parts of the query are instructions and which are data references.&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="c1"&gt;-- Hard to scan&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Much clearer&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One clause per line
&lt;/h3&gt;

&lt;p&gt;Each major clause starts on its own line: &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;FROM&lt;/code&gt;, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN&lt;/code&gt;, &lt;code&gt;GROUP BY&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt;, &lt;code&gt;HAVING&lt;/code&gt;, &lt;code&gt;LIMIT&lt;/code&gt;. This makes the structure of the query immediately visible — you can see at a glance what tables are involved, what conditions apply, and how results are sorted.&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="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Indentation
&lt;/h3&gt;

&lt;p&gt;Indent the contents of a clause by four spaces. This applies to the column list after &lt;code&gt;SELECT&lt;/code&gt;, to conditions after &lt;code&gt;WHERE&lt;/code&gt; when there are multiple, and to subqueries.&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="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Leading AND / OR
&lt;/h3&gt;

&lt;p&gt;When a &lt;code&gt;WHERE&lt;/code&gt; clause has multiple conditions, start each condition with the logical operator at the beginning of the line rather than the end. This makes it trivial to comment out individual conditions during debugging.&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="c1"&gt;-- Trailing AND — hard to comment out individual conditions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
      &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt;
      &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;

&lt;span class="c1"&gt;-- Leading AND — easy to comment out individual conditions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explicit column lists
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SELECT *&lt;/code&gt; is fine for exploratory queries at a REPL or in a migration test. It should never appear in production application code. Explicit column lists make queries self-documenting, prevent breakage when columns are added to a table, and avoid fetching data you don't need.&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="c1"&gt;-- Never in production code&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- What the query actually needs&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Table aliases
&lt;/h3&gt;

&lt;p&gt;Use short, meaningful aliases when joining multiple tables. Always define the alias in the &lt;code&gt;FROM&lt;/code&gt; or &lt;code&gt;JOIN&lt;/code&gt; clause and use it consistently throughout.&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="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;plans&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Formatting JOINs
&lt;/h2&gt;

&lt;p&gt;Each &lt;code&gt;JOIN&lt;/code&gt; and its &lt;code&gt;ON&lt;/code&gt; condition go on separate lines. The join type should always be written explicitly — never just &lt;code&gt;JOIN&lt;/code&gt;, which defaults to &lt;code&gt;INNER JOIN&lt;/code&gt; and hides intent. When the &lt;code&gt;ON&lt;/code&gt; condition spans multiple columns, each condition gets its own line:&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;coupons&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coupon_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;order_items&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;oi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deleted_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Subqueries
&lt;/h2&gt;

&lt;p&gt;Indent subqueries by four spaces relative to the outer query. The opening parenthesis stays on the same line as the clause it belongs to; the closing parenthesis goes on its own line.&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="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;completed_order_count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For subqueries in a &lt;code&gt;WHERE&lt;/code&gt; clause:&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="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;subscriptions&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&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;h2&gt;
  
  
  CTEs — Common Table Expressions
&lt;/h2&gt;

&lt;p&gt;CTEs (&lt;code&gt;WITH&lt;/code&gt; clauses) are one of the most underused SQL features for readability. Instead of nesting subqueries three levels deep, you name each logical step and reference it by name. Format each CTE with the name and &lt;code&gt;AS&lt;/code&gt; on the first line, the query indented inside the parentheses, and each CTE separated by a comma and a blank line.&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="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;active_users&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'guest'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="n"&gt;recent_orders&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;order_count&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'30 days'&lt;/span&gt;
    &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;orders_last_30_days&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;active_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;recent_orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;orders_last_30_days&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CTEs don't just improve formatting — they change how you write queries. When you can name a logical step, you start thinking in terms of "first get active users, then find their recent orders, then join them" rather than writing the whole thing inside-out as nested subqueries.&lt;/p&gt;




&lt;h2&gt;
  
  
  CASE expressions
&lt;/h2&gt;

&lt;p&gt;Format &lt;code&gt;CASE&lt;/code&gt; expressions with each &lt;code&gt;WHEN&lt;/code&gt; and the &lt;code&gt;ELSE&lt;/code&gt; on its own indented line, and &lt;code&gt;END&lt;/code&gt; at the same indentation level as &lt;code&gt;CASE&lt;/code&gt;:&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="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CASE&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;subscription_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pro'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Pro user'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;subscription_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'team'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Team member'&lt;/span&gt;
        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;trial_expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'Trial'&lt;/span&gt;
        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'Free'&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;user_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Comments
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;--&lt;/code&gt; for single-line comments and &lt;code&gt;/* */&lt;/code&gt; for multi-line blocks. Comments in SQL are especially valuable because queries often encode business logic that isn't obvious from the SQL itself.&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="c1"&gt;-- Exclude soft-deleted records and internal test accounts&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;deleted_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%@devcrate.net'&lt;/span&gt;

&lt;span class="cm"&gt;/*
  This CTE handles the edge case where a user can have
  multiple active subscriptions during a plan change window.
  We take the most recently created one.
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dialect differences worth knowing
&lt;/h2&gt;

&lt;p&gt;The conventions above apply across PostgreSQL, MySQL, SQLite, and SQL Server. A few syntax differences are worth knowing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;String quoting:&lt;/strong&gt; Standard SQL uses single quotes for strings. MySQL also accepts double quotes by default, but this is non-standard and should be avoided. PostgreSQL uses single quotes strictly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identifier quoting:&lt;/strong&gt; PostgreSQL uses double quotes (&lt;code&gt;"My Column"&lt;/code&gt;). MySQL uses backticks (&lt;code&gt;`My Column`&lt;/code&gt;). SQL Server uses square brackets (&lt;code&gt;[My Column]&lt;/code&gt;). Avoid column or table names that require quoting entirely — it adds noise to every query that references them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LIMIT vs TOP vs FETCH:&lt;/strong&gt; PostgreSQL, MySQL, and SQLite use &lt;code&gt;LIMIT n&lt;/code&gt;. SQL Server uses &lt;code&gt;SELECT TOP n&lt;/code&gt;. Standard SQL uses &lt;code&gt;FETCH FIRST n ROWS ONLY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Date functions:&lt;/strong&gt; Date arithmetic varies significantly. &lt;code&gt;NOW()&lt;/code&gt; works in PostgreSQL and MySQL. SQLite uses &lt;code&gt;datetime('now')&lt;/code&gt;. &lt;code&gt;INTERVAL&lt;/code&gt; syntax also differs. This is one of the most common sources of queries that work in development but break in production when the databases differ.&lt;/p&gt;




&lt;h2&gt;
  
  
  A formatter won't replace judgment
&lt;/h2&gt;

&lt;p&gt;Autoformatters are useful — they eliminate whitespace debates and catch obvious inconsistencies. But SQL formatting judgment goes beyond what a formatter can enforce. Knowing when to use a CTE vs a subquery, when to break a long condition across multiple lines, when an alias adds clarity vs noise — these require understanding the query, not just the syntax.&lt;/p&gt;

&lt;p&gt;The goal of formatting is to make the query's intent legible to someone reading it cold. A query that passes a linter but buries its logic in three levels of nested subqueries with cryptic single-letter aliases hasn't achieved that goal.&lt;/p&gt;




&lt;p&gt;If you want to format an existing query quickly, &lt;a href="https://devcrate.net/sql/" rel="noopener noreferrer"&gt;DevCrate's SQL Formatter&lt;/a&gt; runs entirely in your browser — paste any query and it applies consistent indentation and keyword casing. Nothing leaves your machine.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Regex cheat sheet for developers — patterns you'll actually use</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 15 Apr 2026 22:27:44 +0000</pubDate>
      <link>https://dev.to/willivan0706/regex-cheat-sheet-for-developers-patterns-youll-actually-use-2cep</link>
      <guid>https://dev.to/willivan0706/regex-cheat-sheet-for-developers-patterns-youll-actually-use-2cep</guid>
      <description>&lt;p&gt;Regex is one of those things that's extremely useful once you have it, and extremely easy to forget the moment you stop using it. Most developers don't need to memorize the full syntax — they need a reference they trust that they can grab patterns from quickly.&lt;/p&gt;

&lt;p&gt;This post is that reference. Every pattern here is real, tested, and explained. At the bottom there's a link to &lt;a href="https://devcrate.net/regex/" rel="noopener noreferrer"&gt;DevCrate's Regex Studio&lt;/a&gt; where you can paste any pattern and test it against your own input immediately.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick syntax refresher
&lt;/h2&gt;

&lt;p&gt;Before the patterns — a brief reminder of the building blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.        any character except newline
\d       digit (0–9)
\D       non-digit
\w       word character (a-z, A-Z, 0-9, _)
\W       non-word character
\s       whitespace (space, tab, newline)
\S       non-whitespace
^        start of string
$        end of string
\b       word boundary
[abc]    character class: a, b, or c
[^abc]   negated class: not a, b, or c
[a-z]    range: a through z
(abc)    capture group
(?:abc)  non-capturing group
a|b      alternation: a or b

Quantifiers
*        0 or more (greedy)
+        1 or more (greedy)
?        0 or 1
{n}      exactly n
{n,}     n or more
{n,m}    between n and m
*?       0 or more (lazy)
+?       1 or more (lazy)

Flags (JavaScript)
i        case-insensitive
g        global (find all matches)
m        multiline (^ and $ match line start/end)
s        dotAll (. matches newline too)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Email addresses
&lt;/h2&gt;

&lt;p&gt;There is no single "correct" email regex — the RFC is notoriously complex and full email validation is best left to a library or a confirmation link. For most use cases, you need something that catches obvious mistakes without being too strict.&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="c1"&gt;// Practical — catches most real addresses&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;@]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;@[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;@]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;@]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// Stricter — requires valid TLD characters, no consecutive dots&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9._%+&lt;/span&gt;&lt;span class="se"&gt;\-]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9.&lt;/span&gt;&lt;span class="se"&gt;\-]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]{2,}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first pattern reads as: one or more characters that aren't whitespace or @, then @, then the same again, then a dot, then the same again. It'll catch &lt;code&gt;user@example.com&lt;/code&gt;, &lt;code&gt;user+tag@sub.domain.io&lt;/code&gt;, and will correctly reject &lt;code&gt;notanemail&lt;/code&gt; and &lt;code&gt;@nodomain&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  URLs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Match http/https URLs&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.?&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;i
&lt;/span&gt;
&lt;span class="c1"&gt;// Match any URL including bare domains&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?(?:&lt;/span&gt;&lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="err"&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;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;@:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="o"&gt;+~&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;()]{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;(?:[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;()@:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="sr"&gt;/=]*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;

&lt;span class="c1"&gt;// Extract just the domain from a full URL&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;)?(?:&lt;/span&gt;&lt;span class="nx"&gt;www&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.)?([&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;i&lt;/span&gt;
&lt;span class="c1"&gt;// → match[1] is the domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URL regex is notoriously tricky. For most validation tasks — form input, user-submitted links — it's often cleaner to use &lt;code&gt;new URL(input)&lt;/code&gt; in JavaScript and catch the error if it's invalid. The regex above is best used for extracting URLs from arbitrary text, not strict validation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phone numbers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// US phone — accepts (555) 555-5555, 555-555-5555, 5555555555&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;]?[(]?[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}[)]?[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.]?[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.]?[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// International — starts with + and country code&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\+?[&lt;/span&gt;&lt;span class="sr"&gt;1-9&lt;/span&gt;&lt;span class="se"&gt;]\d{6,14}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;

&lt;span class="c1"&gt;// Strip all non-digits before matching (recommended)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\D&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{10}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// US number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strip-then-match approach is usually the most practical. Users enter phone numbers in too many formats to catch them all with a single pattern — normalizing first makes the validation much simpler.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dates
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ISO 8601 — 2026-04-15&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// US format — 04/15/2026&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;1-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|1&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-2&lt;/span&gt;&lt;span class="se"&gt;])\/(&lt;/span&gt;&lt;span class="sr"&gt;0&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;1-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;12&lt;/span&gt;&lt;span class="se"&gt;]\d&lt;/span&gt;&lt;span class="sr"&gt;|3&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;01&lt;/span&gt;&lt;span class="se"&gt;])\/\d{4}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;

&lt;span class="c1"&gt;// Any separator — 2026-04-15 or 2026/04/15 or 2026.04.15&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}[&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.](&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.](&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These patterns validate format but not logical validity — they'll accept February 31st. For actual date validation, parse with &lt;code&gt;new Date()&lt;/code&gt; and check &lt;code&gt;isNaN()&lt;/code&gt;, or use a library like date-fns.&lt;/p&gt;




&lt;h2&gt;
  
  
  IP addresses
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// IPv4 — matches 0.0.0.0 to 255.255.255.255&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;?)(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;?)){&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// IPv6 — full address&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]{1,4}&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;){7}[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]{1,4}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The IPv4 pattern is worth understanding: &lt;code&gt;25[0-5]&lt;/code&gt; matches 250–255, &lt;code&gt;2[0-4]\d&lt;/code&gt; matches 200–249, and &lt;code&gt;[01]?\d\d?&lt;/code&gt; matches 0–199.&lt;/p&gt;




&lt;h2&gt;
  
  
  URL slugs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Validate a slug — lowercase letters, numbers, hyphens only&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;(?:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&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;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// Generate a slug from a title&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w\s&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// remove non-word chars&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\s&lt;/span&gt;&lt;span class="sr"&gt;_-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&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="c1"&gt;// spaces/underscores → hyphens&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^-+|-+$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// trim leading/trailing hyphens&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Password rules
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// At least 8 chars, one uppercase, one lowercase, one digit&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;(?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;])(?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;])(?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;).{&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,}&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// At least 8 chars, one uppercase, one lowercase, one digit, one special char&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(?=&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;])(?=&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;])(?=&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;\d)(?=&lt;/span&gt;&lt;span class="sr"&gt;.*&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;@$!%*?&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="sr"&gt;_#^&lt;/span&gt;&lt;span class="se"&gt;])[&lt;/span&gt;&lt;span class="sr"&gt;A-Za-z&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;@$!%*?&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="sr"&gt;_#^&lt;/span&gt;&lt;span class="se"&gt;]{8,}&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;

&lt;span class="c1"&gt;// Check individual rules (more user-friendly)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;minLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;hasUpper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;hasLower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;hasDigit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;hasSpecial&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nx"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;@$!%*?&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;\-&lt;/span&gt;&lt;span class="sr"&gt;_#^&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;str&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;The individual rules approach is friendlier for UI — it lets you show a green checkmark per rule as the user types rather than a single pass/fail.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hex colors
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 3 or 6 digit hex with #&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Fa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Fa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// 3, 4, 6, or 8 digit (includes alpha channel)&lt;/span&gt;
&lt;span class="sr"&gt;/^#&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;A-Fa-f0-9&lt;/span&gt;&lt;span class="se"&gt;]{3,4}&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Fa-f0-9&lt;/span&gt;&lt;span class="se"&gt;]{6}&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;A-Fa-f0-9&lt;/span&gt;&lt;span class="se"&gt;]{8})&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;

&lt;span class="c1"&gt;// Extract hex colors from a CSS file&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(?&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Fa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Fa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common code patterns
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// UUID v4&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="nx"&gt;ab&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;

&lt;span class="c1"&gt;// JWT — three base64url segments separated by dots&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Za&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&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="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Za&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&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="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Za&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&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;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// Hex string (any length)&lt;/span&gt;
&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;

&lt;span class="c1"&gt;// Alphanumeric with underscores and hyphens (good for IDs/usernames)&lt;/span&gt;
&lt;span class="o"&gt;/^&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;zA&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;Z0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="o"&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;$&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Extracting things from text
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Extract all URLs from text&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/https&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\/\/[^\s]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Extract all hashtags&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/#&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z&lt;/span&gt;&lt;span class="se"&gt;]\w&lt;/span&gt;&lt;span class="sr"&gt;*/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Extract all @mentions&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mentions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/@&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;a-zA-Z0-9_&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Extract all numbers (including decimals and negatives)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-&lt;/span&gt;&lt;span class="se"&gt;?\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="c1"&gt;// Find duplicate words&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(\w&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\1\b&lt;/span&gt;&lt;span class="sr"&gt;/gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Lookaheads and lookbehinds
&lt;/h2&gt;

&lt;p&gt;These let you match something based on what comes before or after it, without including that context in the match.&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="c1"&gt;// Lookahead: match "price" only if followed by a digit&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt;&lt;span class="p"&gt;(?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;

&lt;span class="c1"&gt;// Negative lookahead: match "http" only if NOT followed by "s"&lt;/span&gt;
&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="p"&gt;(?&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// Lookbehind: match digits only if preceded by "$"&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;=&lt;/span&gt;&lt;span class="se"&gt;\$)\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(\.\d{2})?&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;// Practical: extract value from "amount: 42.50"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(?&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;=amount:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;/i&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;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lookbehinds are supported in modern JavaScript (Node 10+, Chrome 62+, Safari 2019+). If you need older browser support, use capture groups instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  A few things worth remembering
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Greedy vs lazy matching&lt;/strong&gt; trips people up constantly. &lt;code&gt;.*&lt;/code&gt; is greedy — it matches as much as possible. &lt;code&gt;.*?&lt;/code&gt; is lazy — it stops at the first opportunity. If you're extracting content between tags and getting too much, switching from &lt;code&gt;.*&lt;/code&gt; to &lt;code&gt;.*?&lt;/code&gt; usually fixes it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escaping in strings&lt;/strong&gt; adds a layer of confusion. In a JavaScript string literal, &lt;code&gt;\d&lt;/code&gt; needs to be written as &lt;code&gt;\\d&lt;/code&gt; if you're passing the pattern as a string to &lt;code&gt;new RegExp()&lt;/code&gt;. In a regex literal (&lt;code&gt;/\d/&lt;/code&gt;), no double escaping needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test with real data, not invented examples.&lt;/strong&gt; Edge cases worth testing every time: empty string, all whitespace, Unicode characters, very long strings, strings with newlines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test these patterns in your browser
&lt;/h2&gt;

&lt;p&gt;DevCrate's &lt;a href="https://devcrate.net/regex/" rel="noopener noreferrer"&gt;Regex Studio&lt;/a&gt; lets you paste any pattern and test it against real input in your browser — with live match highlighting, capture group inspection, and flag toggles. No install, no account, nothing leaves your machine.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>regex</category>
    </item>
    <item>
      <title>Why Your API Keys and JWTs Are Safer in a Browser-Based Tool</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Fri, 10 Apr 2026 00:06:27 +0000</pubDate>
      <link>https://dev.to/willivan0706/why-your-api-keys-and-jwts-are-safer-in-a-browser-based-tool-2d6i</link>
      <guid>https://dev.to/willivan0706/why-your-api-keys-and-jwts-are-safer-in-a-browser-based-tool-2d6i</guid>
      <description>&lt;p&gt;Here is something most developers never think about: when you paste a JWT or API key into an online debugging tool, that data travels to a server you don't control.&lt;/p&gt;

&lt;p&gt;It gets sent as an HTTP request. It may be logged. It may be stored. It may be analyzed. And even if the tool's privacy policy says otherwise, you have no way to verify what actually happens on the other end.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical risk. It is the default behavior of most popular online developer tools — and it affects things you probably paste into them every day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happens when you use a server-side tool
&lt;/h2&gt;

&lt;p&gt;When you visit a typical online JWT debugger or API tester, your browser sends your input to their server. That server does the computation — decoding, formatting, validating — and sends the result back. The processing happens remotely, not on your machine.&lt;/p&gt;

&lt;p&gt;This architecture is completely normal for many types of web applications. But for developer tools that process authentication tokens, API keys, and sensitive payloads, it creates a real problem.&lt;/p&gt;

&lt;p&gt;Consider what you actually paste into these tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWTs&lt;/strong&gt; — contain user identity claims, session data, and sometimes role or permission information. A valid JWT from a production system is a live credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API keys&lt;/strong&gt; — grant direct access to your services. A leaked API key is an open door.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP headers&lt;/strong&gt; — often include &lt;code&gt;Authorization&lt;/code&gt; tokens, session cookies, and other credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API payloads&lt;/strong&gt; — may contain PII, internal data structures, or business logic you wouldn't want exposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL queries&lt;/strong&gt; — can reveal your database schema, table names, and data relationships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is sensitive. And every one of them gets sent to a third-party server when you use a conventional online tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The browser-based alternative
&lt;/h2&gt;

&lt;p&gt;A browser-based tool works differently. When you paste a JWT into a browser-based JWT debugger, your browser decodes it locally using JavaScript. The data never leaves your machine. There is no HTTP request to a remote server. There is no server at all — just your browser running code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The key distinction:&lt;/strong&gt; server-side tools process your data on their infrastructure. Browser-based tools process your data on your hardware. One requires trust in a third party. The other requires only trust in your own machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is not a new idea — it is how many security-conscious tools have worked for years. Password managers, encryption tools, and cryptographic utilities have long prioritized local processing for exactly this reason. Developer tools are catching up.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it matters most
&lt;/h2&gt;

&lt;p&gt;The risk profile varies by what you are pasting. Decoding a JWT from a test environment with fake data is low risk regardless of where it is processed. But the habits you build in development tend to follow you into production. And the moment you paste a production token into a server-side tool — even once, even accidentally — you have sent a live credential to a server you do not control.&lt;/p&gt;

&lt;p&gt;The cases where it matters most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production JWTs&lt;/strong&gt; — decoded routinely during debugging, often contain live session data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party API keys&lt;/strong&gt; — Stripe, AWS, GitHub, SendGrid — any key you test with an HTTP client&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internal REST API responses&lt;/strong&gt; — may expose data structures, IDs, and relationships not meant to be public&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database queries&lt;/strong&gt; — reveal schema details that aid attackers doing reconnaissance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP response headers&lt;/strong&gt; — server fingerprinting information, session identifiers, internal routing details&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to look for in a tool
&lt;/h2&gt;

&lt;p&gt;The simplest test: open your browser's network tab while using a developer tool. If you see an outbound request being made when you paste or submit data, your input is leaving your machine. If you see nothing — no request at all — the processing is happening locally.&lt;/p&gt;

&lt;p&gt;A few other signals worth checking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Does the tool work offline?&lt;/strong&gt; A genuinely browser-based tool should function with no internet connection after the page loads. If it stops working offline, it depends on a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is the source available?&lt;/strong&gt; Open source tools let you verify exactly what runs in the browser. Closed tools require you to take their word for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the URL change when you submit data?&lt;/strong&gt; If your input appears in the URL or triggers a navigation, it was sent to a server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A practical approach
&lt;/h2&gt;

&lt;p&gt;The safest default is to treat any online developer tool as a potential data sink until proven otherwise. That means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use browser-based tools for anything involving real credentials or sensitive data&lt;/li&gt;
&lt;li&gt;Keep test and production tokens separate, and use test tokens when exploring new tools&lt;/li&gt;
&lt;li&gt;Check the network tab when you use a tool for the first time&lt;/li&gt;
&lt;li&gt;Rotate any credentials that may have been sent to a server you do not control&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this requires paranoia. It just requires being deliberate about where sensitive data goes — which is exactly the kind of thinking that separates careful developers from careless ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built DevCrate this way
&lt;/h2&gt;

&lt;p&gt;When I started building &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt;, the browser-based architecture was not an afterthought — it was the starting point. I wanted tools I could use myself without thinking twice about what I was pasting into them.&lt;/p&gt;

&lt;p&gt;Every tool on DevCrate — the &lt;a href="https://devcrate.net/jwt/" rel="noopener noreferrer"&gt;JWT Debugger&lt;/a&gt;, the &lt;a href="https://devcrate.net/rest/" rel="noopener noreferrer"&gt;REST Client&lt;/a&gt;, the &lt;a href="https://devcrate.net/headers/" rel="noopener noreferrer"&gt;HTTP Headers Inspector&lt;/a&gt;, all 22 of them — processes your data entirely in your browser. You can verify this yourself: open the network tab, paste something in, and watch nothing happen. No request. No server. No question about where your data went.&lt;/p&gt;

&lt;p&gt;It also means the tools work offline, load instantly, and have no backend infrastructure to maintain or secure. The privacy benefit and the architectural simplicity point in the same direction.&lt;/p&gt;

&lt;p&gt;That is the kind of tool I want to use. I built DevCrate so other developers could have the same option.&lt;/p&gt;




&lt;p&gt;I'm William, the developer behind DevCrate. If this made you reconsider one tool in your workflow, it was worth writing. If you want to verify DevCrate's claims yourself, open DevTools → Network and paste something into any tool. You will see exactly zero outbound requests.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How to Test WebSocket Connections in the Browser (No Install Required)</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 08 Apr 2026 23:19:08 +0000</pubDate>
      <link>https://dev.to/willivan0706/how-to-test-websocket-connections-in-the-browser-no-install-required-1p68</link>
      <guid>https://dev.to/willivan0706/how-to-test-websocket-connections-in-the-browser-no-install-required-1p68</guid>
      <description>&lt;p&gt;WebSocket bugs are some of the hardest to debug. The connection looks fine, the server starts without errors, but something isn't working — and you don't know if the problem is your client code, your server, or the connection itself.&lt;/p&gt;

&lt;p&gt;The fastest way to rule out your client code entirely is to test the WebSocket endpoint directly in a browser-based tester. No install, no dependencies, no writing a throwaway script just to send one message.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a WebSocket?
&lt;/h2&gt;

&lt;p&gt;A WebSocket is a persistent, two-way communication channel between a browser and a server. Unlike HTTP — where the client sends a request and waits for a response — WebSockets let the server push data to the client at any time without being asked.&lt;/p&gt;

&lt;p&gt;The connection starts as a standard HTTP request and then upgrades via a handshake. Once established, both sides can send messages freely until one of them closes the connection.&lt;/p&gt;

&lt;p&gt;WebSocket URLs use &lt;code&gt;ws://&lt;/code&gt; for unencrypted connections and &lt;code&gt;wss://&lt;/code&gt; for SSL-encrypted ones — equivalent to &lt;code&gt;http://&lt;/code&gt; and &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Test in the Browser First?
&lt;/h2&gt;

&lt;p&gt;When something isn't working, you want to isolate the problem as fast as possible. A browser-based tester lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify the server is reachable before writing a single line of client code&lt;/li&gt;
&lt;li&gt;Test authentication flows by sending auth messages manually&lt;/li&gt;
&lt;li&gt;Confirm the exact format of messages your server expects&lt;/li&gt;
&lt;li&gt;Check that your server handles connection and disconnection events correctly&lt;/li&gt;
&lt;li&gt;Monitor live data streams without instrumenting your app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the tester can connect and your server responds correctly, the problem is in your client code. If the tester can't connect, the problem is in your server or network config. That distinction alone saves hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Test a WebSocket Endpoint
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://devcrate.net/websocket/" rel="noopener noreferrer"&gt;DevCrate's WebSocket Tester&lt;/a&gt; — it runs entirely in your browser with no login required and no data routed through any server. Your messages go directly from your browser to your WebSocket server.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Enter your endpoint URL
&lt;/h3&gt;

&lt;p&gt;Paste your WebSocket URL into the connection field. The protocol is a separate dropdown — just enter the host and path without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo.websocket.org
localhost:3000/ws
your-server.com/api/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select &lt;code&gt;wss://&lt;/code&gt; for secure or production connections, or &lt;code&gt;ws://&lt;/code&gt; for local development.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add a subprotocol if required
&lt;/h3&gt;

&lt;p&gt;Some servers (Socket.io, STOMP, custom APIs) require a subprotocol header. Enter it in the subprotocol field before connecting — for example &lt;code&gt;chat&lt;/code&gt; or &lt;code&gt;v1.protocol&lt;/code&gt;. This is a common reason connections get rejected silently.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Click Connect
&lt;/h3&gt;

&lt;p&gt;The status indicator turns green when the connection is established. You'll see the connection event logged with a timestamp immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Send a message
&lt;/h3&gt;

&lt;p&gt;Type a message in the send field and hit Enter or click Send. For JSON payloads, just type valid JSON directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prices"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BTC"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response appears in the message log instantly, color-coded by direction — blue for sent, green for received. If the server returns JSON, it's automatically pretty-printed so you can read it without squinting at a single line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Testing Scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Testing an authentication flow
&lt;/h3&gt;

&lt;p&gt;Most real WebSocket APIs require authentication immediately after connecting. Send the auth message first before anything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer your-token-here"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch the server's response. A success acknowledgment means your auth flow works. An error or immediate close means your token format is wrong or the server expected something different.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing a subscription API
&lt;/h3&gt;

&lt;p&gt;Many real-time APIs use a subscribe/unsubscribe pattern. Once you send a subscribe message, the server should start pushing data automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"updates"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fastest way to verify your server is actually pushing data before you wire up your frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring latency
&lt;/h3&gt;

&lt;p&gt;Use the built-in ping button to send a ping message and measure the round-trip time. Useful for checking whether a remote WebSocket server has acceptable latency for your use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing against a public echo server
&lt;/h3&gt;

&lt;p&gt;If you want to verify the tester is working before connecting to your own server, use a public echo server — it reflects every message back to you immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo.websocket.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send anything and you'll see it come straight back. Once you've confirmed that works, connect to your own endpoint. The tester also has one-click buttons for common public test servers built in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging Connection Errors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Connection refused&lt;/strong&gt; — the server isn't running or the port is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection closes immediately&lt;/strong&gt; — the server rejected the handshake. Common causes: missing subprotocol, CORS policy blocking browser connections, or the server expects an auth message within a short timeout window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Messages not arriving&lt;/strong&gt; — check whether the server requires a subscription message before it starts pushing data. Many APIs won't send anything until you explicitly subscribe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ws://&lt;/code&gt; failing in production&lt;/strong&gt; — browsers block mixed content. If your page is served over HTTPS, WebSocket connections must use &lt;code&gt;wss://&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Workflow That Saves the Most Time
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Test the connection manually in the browser tester&lt;/li&gt;
&lt;li&gt;Confirm the exact message format the server expects&lt;/li&gt;
&lt;li&gt;Verify the server's responses look correct&lt;/li&gt;
&lt;li&gt;Then write your client code against a known-working endpoint&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skipping steps 1–3 means debugging your client code and your server simultaneously, which doubles the surface area of the problem. A five-minute manual test upfront can save hours of back-and-forth.&lt;/p&gt;




&lt;p&gt;I'm William, the developer behind &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt;. The WebSocket Tester was one of the most-requested tools when I launched — developers kept running into the same problem of not having a quick way to poke at a WS endpoint without spinning up a throwaway script. I hope this guide saves you some debugging time.&lt;/p&gt;

&lt;p&gt;If you hit a WebSocket scenario this didn't cover, drop it in the comments — I read everything.&lt;/p&gt;

</description>
      <category>websocket</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built 21 Browser-Based Dev Tools in Pure HTML/CSS/JS — Here's What I Learned</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Mon, 06 Apr 2026 00:47:31 +0000</pubDate>
      <link>https://dev.to/willivan0706/i-built-21-browser-based-dev-tools-in-pure-htmlcssjs-heres-what-i-learned-j7d</link>
      <guid>https://dev.to/willivan0706/i-built-21-browser-based-dev-tools-in-pure-htmlcssjs-heres-what-i-learned-j7d</guid>
      <description>&lt;p&gt;About three weeks ago I shipped &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt; — a collection of 21 developer utilities that run entirely in your browser. No login. No backend. No frameworks. Just HTML, CSS, and vanilla JavaScript.&lt;/p&gt;

&lt;p&gt;I want to be honest about how it went, because most "I built a thing" posts make it sound cleaner than it was.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I kept reaching for the same tools every day. A JSON formatter. A JWT debugger. A cron expression builder. And every time I landed on a site that wanted my email address, showed me ads, or sent my API payloads through their server, I'd close the tab annoyed.&lt;/p&gt;

&lt;p&gt;So I built the version I actually wanted. Browser-only. Fast. No accounts. No tracking.&lt;/p&gt;

&lt;p&gt;What I thought would take a weekend took three weeks. Here's what slowed me down.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing that actually broke me: CSS consistency across 21 tools
&lt;/h2&gt;

&lt;p&gt;I expected the hard part to be the JavaScript — the JWT parsing, the regex engine, the diff algorithm. That stuff was fine. The thing that genuinely ground me down was keeping 21 pages visually consistent.&lt;/p&gt;

&lt;p&gt;Every tool is its own HTML file. No component system. No shared templates. Just a &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; to a &lt;code&gt;shared.css&lt;/code&gt; and the assumption that I'd stay disciplined.&lt;/p&gt;

&lt;p&gt;I did not stay disciplined.&lt;/p&gt;

&lt;p&gt;By tool 8 or 9 I started copying from whichever page was open, making small tweaks, and moving on. By tool 15 I had three slightly different versions of the breadcrumb. Two different spacings on the pro banner. One page where &lt;code&gt;--text3&lt;/code&gt; was &lt;code&gt;#a07840&lt;/code&gt; instead of &lt;code&gt;#ad7146&lt;/code&gt;. A nav rule that was catching the breadcrumb element because both used a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; tag and I hadn't scoped the CSS selector properly.&lt;/p&gt;

&lt;p&gt;None of these were obvious. They only showed up when I screenshotted every page side by side and started comparing.&lt;/p&gt;

&lt;p&gt;The fix wasn't glamorous. I picked one page as the gold standard, wrote a Python audit script that compared every other page against it, and worked through the failures one by one. It took longer than building three of the actual tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd do differently:&lt;/strong&gt; establish a locked template file on day one. Copy it for every new page. Never "borrow" from a page that's already drifted. Treat it like a component even if you're not using a framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a Pro subscription on a static site
&lt;/h2&gt;

&lt;p&gt;DevCrate has a Pro tier — $19/month via Lemon Squeezy. No server, no database, no user accounts. The license key gets stored in &lt;code&gt;localStorage&lt;/code&gt; and gates certain features client-side.&lt;/p&gt;

&lt;p&gt;This is not Fort Knox. A determined person could open DevTools and flip a value. I'm fine with that. The people willing to do that weren't going to pay anyway. The people who pay just want it to work.&lt;/p&gt;

&lt;p&gt;Getting Lemon Squeezy wired up was straightforward. The trickier part was the UX — what happens when someone pays on their phone, then opens the site on their laptop? I built a restore flow where you re-enter your purchase email and license key to re-activate on a new browser. Simple, and it works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going framework-free intentionally
&lt;/h2&gt;

&lt;p&gt;People ask why I didn't use React or a static site generator. The honest answer is I wanted zero build tooling. No npm. No webpack. No &lt;code&gt;node_modules&lt;/code&gt; folder that somehow weighs 300MB for a site with no dependencies.&lt;/p&gt;

&lt;p&gt;The tradeoff is real — consistency is harder without components, as I learned the painful way. But the result is a site that deploys by pushing HTML files to Cloudflare Pages, loads in under a second, and has a Lighthouse score I'm proud of.&lt;/p&gt;

&lt;p&gt;For a project like this — lots of small isolated tools, each with its own UI — vanilla actually fits. The overhead of a framework would have been felt in every page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three weeks in
&lt;/h2&gt;

&lt;p&gt;The site is indexed. It has its first external backlinks. A few people are using it daily based on the Pro signups.&lt;/p&gt;

&lt;p&gt;There's still a lot to build — YouTube demo videos, saved configurations synced to an account, a public API. It's all on the roadmap.&lt;/p&gt;

&lt;p&gt;If you're a developer who's tired of dev tools that want your data, &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;give DevCrate a try&lt;/a&gt;. Everything is free to start. If it saves you time, the Pro plan is there when you need it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>When AI Over-Engineers: A DevCrate Case Study</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Thu, 02 Apr 2026 07:25:05 +0000</pubDate>
      <link>https://dev.to/willivan0706/when-ai-over-engineers-why-dumb-copy-paste-is-sometimes-the-smartest-solution-126k</link>
      <guid>https://dev.to/willivan0706/when-ai-over-engineers-why-dumb-copy-paste-is-sometimes-the-smartest-solution-126k</guid>
      <description>&lt;p&gt;As developers, we are trained to abhor repetition. The DRY principle (Don't Repeat Yourself ) is drilled into us from day one. When we see three files that need the same update, our instinct is to write a script, create a component, or build an abstraction. &lt;/p&gt;

&lt;p&gt;Recently, while working on &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt; — a suite of privacy-first, browser-based developer tools — I encountered a situation where this instinct, amplified by an AI assistant, led to a cascading series of failures. The solution turned out to be the exact opposite of what we are taught: a literal, manual copy-paste.&lt;/p&gt;

&lt;p&gt;This is a story about the over-engineering bias inherent in AI agents, and why sometimes the "dumbest" solution is actually the smartest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Visual Inconsistencies
&lt;/h2&gt;

&lt;p&gt;DevCrate consists of over a dozen individual tool pages (JSON formatter, JWT debugger, REST client, etc.). During a recent audit, we noticed visual inconsistencies in the hero sections of three specific pages: the CSV tool, the JWT Builder, and the HTTP Headers Inspector. &lt;/p&gt;

&lt;p&gt;They were missing a "PRO ACTIVE" pill badge, an eyebrow label (&lt;code&gt;// FREE ONLINE TOOL&lt;/code&gt;), and had incorrect spacing compared to our canonical template, the REST Client page.&lt;/p&gt;

&lt;p&gt;The goal was simple: make the hero sections of those three broken pages look exactly like the REST Client page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI's Approach: Scripts and Abstractions
&lt;/h2&gt;

&lt;p&gt;I asked my AI assistant to fix the three pages using the REST Client page as a template. &lt;/p&gt;

&lt;p&gt;The AI's immediate instinct was to write a script. It analyzed the DOM structure of the REST Client page, extracted the "correct" header and footer patterns, and wrote a Python script using BeautifulSoup to programmatically inject these patterns across the files.&lt;/p&gt;

&lt;p&gt;It failed. The script made assumptions about the structure of the broken pages that weren't entirely accurate. It ended up nesting &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; elements, corrupting navigation links, and breaking the homepage. &lt;/p&gt;

&lt;p&gt;We reverted the site and tried again. The AI wrote a &lt;em&gt;better&lt;/em&gt; script. It failed again, this time breaking the layout in different ways. &lt;/p&gt;

&lt;p&gt;Why did this happen? Because AI agents are trained on vast amounts of code and documentation that heavily weight abstraction, automation, and scalable solutions. When an AI sees a task like "make these files match this template," its default behavior is to generalize: write a function, loop over files, parse the DOM, apply transformations. &lt;/p&gt;

&lt;p&gt;This instinct is incredibly useful when you need to process 10,000 files. It is actively harmful when you need to fix exactly three pages and precision matters more than throughput.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Human Insight: Literal Template Replication
&lt;/h2&gt;

&lt;p&gt;After several failed attempts, the human developer stepped in with a crucial insight: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Whenever anyone wants you to use a template, I would bet they mean to use the template as the basis for any new page. You could... use a known page (actually copied) to exactly implement (pasted) the style, spacing, etc. Once that is done, you could just name the file appropriately. You wouldn't change the template except for the explicit content."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This was the lightbulb moment. &lt;/p&gt;

&lt;p&gt;When a user says "use X as a template," they don't mean "extract the abstract structural patterns of X and programmatically apply them to Y." They mean &lt;strong&gt;start with an exact copy of X&lt;/strong&gt;, then change only the content that must differ (title, description, slug, tool-specific functionality). &lt;/p&gt;

&lt;p&gt;Nothing else gets touched. Not the structure, not the spacing, not the class names. The template is sacred.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Copy, Paste, Edit
&lt;/h2&gt;

&lt;p&gt;We abandoned the scripts. Instead, we took the "dumb" approach:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opened the working REST Client page (&lt;code&gt;rest-client/index.html&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Copied the exact HTML structure of its hero section.&lt;/li&gt;
&lt;li&gt;Opened the broken &lt;code&gt;csv/index.html&lt;/code&gt; page.&lt;/li&gt;
&lt;li&gt;Replaced its entire hero section with the copied HTML.&lt;/li&gt;
&lt;li&gt;Changed exactly five lines of text: the page title, meta description, breadcrumb slug, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; title, and the description paragraph.&lt;/li&gt;
&lt;li&gt;Repeated for the other two pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It worked perfectly on the first try. The pages were visually identical to the template, the tool-specific JavaScript remained intact, and there were zero unintended side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson: Knowing When Not to Automate
&lt;/h2&gt;

&lt;p&gt;The simplest solution that works is almost always the best solution. Automation and abstraction have their place, but not when you are dealing with a small number of files where precision is paramount. &lt;/p&gt;

&lt;p&gt;A manual copy-paste of a known-good file is deterministic — it produces exactly what you can see working. A script that tries to reconstruct that same result from rules and patterns is probabilistic — it might work, or it might silently break things in ways you don't notice until the user sees a mangled page.&lt;/p&gt;

&lt;p&gt;This is a widespread pattern across AI agents. They lack the practical wisdom to recognize when "dumb" is smart. They default to the most sophisticated approach because sophistication is what gets rewarded in their training data. Nobody writes a blog post about how they copy-pasted a file. People write blog posts about elegant scripts.&lt;/p&gt;

&lt;p&gt;But as developers working alongside AI, we need to recognize this bias. We need to provide concrete, situation-specific guidance to bridge the gap between what AI agents default to and what actually works in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Human Side: Learning to Prompt
&lt;/h2&gt;

&lt;p&gt;It is easy to frame this as a story about what the AI got wrong. But the human in this situation learned something too.&lt;/p&gt;

&lt;p&gt;The first prompt was vague: "Fix these three pages to match the REST Client page." That sounds clear to a human — any developer on your team would know exactly what to do. But to an AI agent, it is an open-ended engineering problem. The AI heard "match" and reached for the most robust, generalizable way to achieve that. It did what it was asked. It just interpreted the ask at the wrong level of abstraction.&lt;/p&gt;

&lt;p&gt;The prompt that actually worked was radically more specific: "Copy the REST Client page. Paste it. Rename it. Change only the title, description, and slug." That left no room for interpretation. There was no ambiguity about method, scope, or approach. The AI did not need to decide &lt;em&gt;how&lt;/em&gt; to solve the problem because the prompt &lt;em&gt;was&lt;/em&gt; the solution.&lt;/p&gt;

&lt;p&gt;This is the real skill of working with AI in 2026: learning to prompt at the right level of concreteness. When you want creativity and exploration, prompt loosely. When you want precision and fidelity, prompt like you are writing a recipe — step by step, with no room for improvisation. The failure was not just that the AI over-engineered. It was that the initial prompt gave it permission to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intelligence vs. Wisdom
&lt;/h2&gt;

&lt;p&gt;This experience forced a re-evaluation of what we mean by "intelligence" in the context of AI. &lt;/p&gt;

&lt;p&gt;Before this, one might define intelligence as pattern recognition, reasoning ability, or problem-solving capacity. Those definitions favor what AI is already good at: processing information, finding structure, generating solutions at scale.&lt;/p&gt;

&lt;p&gt;But this experience exposed a gap. The AI had all the information it needed. It could parse HTML, understand DOM structures, write syntactically correct Python, and reason about what "matching a template" should mean. By any conventional measure of intelligence, it was well-equipped to solve the problem. And it failed repeatedly — not because it lacked capability, but because it lacked judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intelligence, it turns out, is knowing what not to do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the ability to look at a problem and correctly assess its actual complexity, not its theoretical complexity. A script that normalizes hero sections across N files is a legitimate solution to a legitimate class of problems. But the problem in front of us was not that problem. It was three files that needed to look like a fourth file. The intelligent response was to recognize that the problem was small, concrete, and high-stakes for precision — and to match the solution to those properties.&lt;/p&gt;

&lt;p&gt;A truly intelligent agent would have asked: "What is the simplest thing that could work here?" and started there. Instead, it asked: "What is the most complete and generalizable thing I could build?" — which is a different question entirely, and the wrong one for the situation.&lt;/p&gt;

&lt;p&gt;There is a word for what was missing, and it is not "knowledge" or "reasoning." It is &lt;strong&gt;wisdom&lt;/strong&gt; — the practical sense of proportion that tells you when a problem deserves a five-line edit and when it deserves a five-hundred-line script. Wisdom is what lets a senior developer finish in five minutes what a junior developer spends two hours automating. It is not about knowing more. It is about knowing what matters.&lt;/p&gt;

&lt;p&gt;If intelligence is the ability to solve problems, wisdom is the ability to correctly size them first. The AI had the former. It did not have the latter. And without the latter, the former caused more harm than good.&lt;/p&gt;

&lt;p&gt;Sometimes, the best code is the code you don't write. Sometimes, the best tool is &lt;code&gt;Ctrl+C&lt;/code&gt; and &lt;code&gt;Ctrl+V&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was inspired by a real debugging session while building &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt;, a suite of 100% browser-based, privacy-first developer tools.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Cron expressions explained — a complete guide with real examples</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 01 Apr 2026 19:21:18 +0000</pubDate>
      <link>https://dev.to/willivan0706/cron-expressions-explained-a-complete-guide-with-real-examples-4pj8</link>
      <guid>https://dev.to/willivan0706/cron-expressions-explained-a-complete-guide-with-real-examples-4pj8</guid>
      <description>&lt;p&gt;Cron is one of those tools that every developer encounters eventually. You need to run a database backup every night, send a weekly digest email, or poll an API every five minutes — and someone mentions cron. You look up the syntax, copy something from Stack Overflow, it works, and you move on. Then six months later you need to change the schedule and you're staring at five numbers wondering what each one means again.&lt;/p&gt;

&lt;p&gt;This guide is meant to be the one you save. After reading it you should be able to read and write any cron expression from scratch, without guessing.&lt;/p&gt;




&lt;h2&gt;
  
  
  What cron actually is
&lt;/h2&gt;

&lt;p&gt;Cron is a time-based job scheduler built into Unix-like operating systems. A &lt;strong&gt;cron job&lt;/strong&gt; is a command or script that cron runs automatically on a defined schedule. The schedule is defined by a &lt;strong&gt;cron expression&lt;/strong&gt; — a string of five fields that describe when to run the job.&lt;/p&gt;

&lt;p&gt;Cron expressions show up everywhere: Linux crontabs, GitHub Actions schedules, AWS EventBridge, Kubernetes CronJobs, Vercel cron functions, Railway cron jobs, and more. The syntax is largely the same across all of them, with minor variations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Anatomy of a cron expression
&lt;/h2&gt;

&lt;p&gt;A standard cron expression has five fields separated by spaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;┌─────────── &lt;span class="n"&gt;minute&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;59&lt;/span&gt;)
│ ┌───────── &lt;span class="n"&gt;hour&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;23&lt;/span&gt;)
│ │ ┌─────── &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;–&lt;span class="m"&gt;31&lt;/span&gt;)
│ │ │ ┌───── &lt;span class="n"&gt;month&lt;/span&gt; (&lt;span class="m"&gt;1&lt;/span&gt;–&lt;span class="m"&gt;12&lt;/span&gt;)
│ │ │ │ ┌─── &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;week&lt;/span&gt; (&lt;span class="m"&gt;0&lt;/span&gt;–&lt;span class="m"&gt;7&lt;/span&gt;, &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;both&lt;/span&gt; &lt;span class="n"&gt;Sunday&lt;/span&gt;)
│ │ │ │ │
* * * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A helpful mnemonic: &lt;strong&gt;M H D M W&lt;/strong&gt; — Minutes, Hours, Days, Months, Weekdays.&lt;/p&gt;


&lt;h2&gt;
  
  
  Field ranges and allowed values
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Special characters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minute&lt;/td&gt;
&lt;td&gt;0–59&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* , - /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hour&lt;/td&gt;
&lt;td&gt;0–23&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* , - /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of month&lt;/td&gt;
&lt;td&gt;1–31&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* , - / ?&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;1–12 or JAN–DEC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* , - /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of week&lt;/td&gt;
&lt;td&gt;0–7 or SUN–SAT (0 and 7 are both Sunday)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;* , - / ?&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Special characters
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;*&lt;/code&gt; — every
&lt;/h3&gt;

&lt;p&gt;An asterisk means "every valid value for this field." &lt;code&gt;*&lt;/code&gt; in the minute field means every minute. &lt;code&gt;*&lt;/code&gt; in the hour field means every hour.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;,&lt;/code&gt; — list
&lt;/h3&gt;

&lt;p&gt;A comma lets you specify multiple values. &lt;code&gt;1,15,30&lt;/code&gt; in the minute field means at minute 1, 15, and 30.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;-&lt;/code&gt; — range
&lt;/h3&gt;

&lt;p&gt;A hyphen defines a range. &lt;code&gt;9-17&lt;/code&gt; in the hour field means every hour from 9am to 5pm inclusive.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;/&lt;/code&gt; — step
&lt;/h3&gt;

&lt;p&gt;A slash defines a step interval. &lt;code&gt;*/5&lt;/code&gt; in the minute field means every 5 minutes. &lt;code&gt;0-30/10&lt;/code&gt; means every 10 minutes between minute 0 and minute 30.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;?&lt;/code&gt; — no specific value
&lt;/h3&gt;

&lt;p&gt;Used in day-of-month and day-of-week fields to mean "I don't care." When you specify a day of week, use &lt;code&gt;?&lt;/code&gt; in the day-of-month field, and vice versa. Not all cron implementations support this — standard Linux crontab uses &lt;code&gt;*&lt;/code&gt; instead.&lt;/p&gt;


&lt;h2&gt;
  
  
  Real examples
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run at midnight every day&lt;/span&gt;
0 0 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run every 5 minutes&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run at 9am Monday through Friday&lt;/span&gt;
0 9 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run at 6am and 6pm every day&lt;/span&gt;
0 6,18 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run at 2:30am on the 1st of every month&lt;/span&gt;
30 2 1 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run every weekday at noon&lt;/span&gt;
0 12 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run at 11:59pm on December 31st&lt;/span&gt;
59 23 31 12 &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run every 15 minutes between 8am and 5pm on weekdays&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt;/15 8-17 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 1-5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Confusing day-of-week numbering
&lt;/h3&gt;

&lt;p&gt;Different systems handle Sunday differently. In standard crontab, Sunday is both 0 and 7. In some systems, 0 is Sunday and 6 is Saturday. In others, 1 is Monday and 7 is Sunday. Always check the documentation for the platform you're using. When in doubt, use the three-letter abbreviation (&lt;code&gt;SUN&lt;/code&gt;, &lt;code&gt;MON&lt;/code&gt;, etc.) if the platform supports it — it's unambiguous.&lt;/p&gt;
&lt;h3&gt;
  
  
  Forgetting timezone
&lt;/h3&gt;

&lt;p&gt;Cron runs in the timezone of the server unless configured otherwise. If your server is UTC and your users are in EST, a job scheduled for &lt;code&gt;0 9 * * *&lt;/code&gt; runs at 4am local time for East Coast users. Always be explicit about timezone. Most modern platforms (GitHub Actions, AWS, Vercel) let you specify timezone separately.&lt;/p&gt;
&lt;h3&gt;
  
  
  Thinking &lt;code&gt;*/5&lt;/code&gt; means "every 5 minutes starting now"
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;*/5&lt;/code&gt; in the minute field means at minutes 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, and 55 — fixed to the clock, not relative to when the job was added. If you add a job at 2:03pm with &lt;code&gt;*/5&lt;/code&gt;, it first runs at 2:05pm, not 2:08pm.&lt;/p&gt;
&lt;h3&gt;
  
  
  Using both day-of-month and day-of-week
&lt;/h3&gt;

&lt;p&gt;If you specify a value in both the day-of-month and day-of-week fields (rather than using &lt;code&gt;*&lt;/code&gt; in one), most cron implementations treat them as an OR condition, not AND. &lt;code&gt;0 0 1 * 1&lt;/code&gt; runs at midnight on the 1st of every month &lt;strong&gt;and&lt;/strong&gt; every Monday — not just on Mondays that fall on the 1st. If you want "the first Monday of the month" you need a workaround, since standard cron can't express that directly.&lt;/p&gt;


&lt;h2&gt;
  
  
  Platform-specific variations
&lt;/h2&gt;

&lt;p&gt;Standard Linux crontab uses the five-field format above. But many platforms extend or modify it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; uses standard five-field cron, always in UTC. The minimum interval is every 5 minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS EventBridge (CloudWatch Events)&lt;/strong&gt; uses a six-field format with an optional seconds field, and uses &lt;code&gt;?&lt;/code&gt; for the day-of-month/day-of-week conflict. It also adds &lt;code&gt;L&lt;/code&gt; (last) and &lt;code&gt;W&lt;/code&gt; (weekday nearest to) special characters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes CronJobs&lt;/strong&gt; use standard five-field cron and support &lt;code&gt;@hourly&lt;/code&gt;, &lt;code&gt;@daily&lt;/code&gt;, &lt;code&gt;@weekly&lt;/code&gt;, &lt;code&gt;@monthly&lt;/code&gt;, and &lt;code&gt;@yearly&lt;/code&gt; shortcuts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vercel and Railway&lt;/strong&gt; use standard five-field cron. Railway requires a minimum interval of 1 minute.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Shorthand expressions
&lt;/h2&gt;

&lt;p&gt;Many cron implementations support named shortcuts for common schedules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shorthand&lt;/th&gt;
&lt;th&gt;Equivalent&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@yearly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 1 1 *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once a year at midnight on January 1st&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@monthly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 1 * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once a month at midnight on the 1st&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 * * 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once a week at midnight on Sunday&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@daily&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 0 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once a day at midnight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@hourly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Once an hour at the start of the hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@reboot&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Once at startup (Linux crontab only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  How to read an unfamiliar expression
&lt;/h2&gt;

&lt;p&gt;When you encounter a cron expression you don't immediately recognize, read it field by field left to right: minute, hour, day-of-month, month, day-of-week. Ask yourself: is this field a specific value, a range, a list, a step, or a wildcard? Build up the meaning piece by piece.&lt;/p&gt;

&lt;p&gt;Take &lt;code&gt;0 */6 * * *&lt;/code&gt;. Minute is &lt;code&gt;0&lt;/code&gt; — on the hour. Hour is &lt;code&gt;*/6&lt;/code&gt; — every 6 hours (0, 6, 12, 18). Day, month, and day-of-week are all wildcards. So: at midnight, 6am, noon, and 6pm, every day.&lt;/p&gt;

&lt;p&gt;Take &lt;code&gt;30 8 * * 1&lt;/code&gt;. Minute &lt;code&gt;30&lt;/code&gt;, hour &lt;code&gt;8&lt;/code&gt;, day-of-month wildcard, month wildcard, day-of-week &lt;code&gt;1&lt;/code&gt; (Monday). So: 8:30am every Monday.&lt;/p&gt;


&lt;h2&gt;
  
  
  Try it without deploying anything
&lt;/h2&gt;

&lt;p&gt;I built the &lt;a href="https://devcrate.net/cron/" rel="noopener noreferrer"&gt;DevCrate cron builder&lt;/a&gt; because I found myself testing expressions by deploying jobs and watching logs — which is a slow, painful way to verify a schedule.&lt;/p&gt;

&lt;p&gt;The tool lets you paste any expression and instantly see the next several run times in plain English, without deploying anything. It also works the other way: pick a schedule from the visual builder and it generates the expression for you. Free, runs entirely in your browser, nothing to sign up for.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://devcrate.net/cron/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevcrate.net%2Flogo-dark.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://devcrate.net/cron/" rel="noopener noreferrer" class="c-link"&gt;
            Free Online Cron Expression Builder — DevCrate
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Build and validate cron expressions with a visual editor. See human-readable output and next 10 run times instantly.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevcrate.net%2Ffavicon.svg"&gt;
          devcrate.net
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>devops</category>
      <category>linux</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A developer's guide to JWTs — how they work and why they break</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:47:14 +0000</pubDate>
      <link>https://dev.to/willivan0706/a-developers-guide-to-jwts-how-they-work-and-why-they-break-82j</link>
      <guid>https://dev.to/willivan0706/a-developers-guide-to-jwts-how-they-work-and-why-they-break-82j</guid>
      <description>&lt;p&gt;Liquid syntax error: Unknown tag 'callout'&lt;/p&gt;
</description>
      <category>jwt</category>
      <category>security</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A developer's guide to JSON — formatting, validation, and common mistakes</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 25 Mar 2026 01:08:44 +0000</pubDate>
      <link>https://dev.to/willivan0706/a-developers-guide-to-json-formatting-validation-and-common-mistakes-5e6d</link>
      <guid>https://dev.to/willivan0706/a-developers-guide-to-json-formatting-validation-and-common-mistakes-5e6d</guid>
      <description>&lt;p&gt;JSON is everywhere.&lt;/p&gt;

&lt;p&gt;It's the format your API returns data in. It's your config files, &lt;br&gt;
your package.json, your database exports, your webhook payloads. &lt;br&gt;
If you write code that talks to anything else, you're working &lt;br&gt;
with JSON every single day.&lt;/p&gt;

&lt;p&gt;And yet it trips developers up constantly — not because it's &lt;br&gt;
complicated, but because it's unforgiving. One missing comma, &lt;br&gt;
one extra bracket, one set of single quotes instead of double, &lt;br&gt;
and the whole thing breaks.&lt;/p&gt;

&lt;p&gt;This guide covers what you actually need to know.&lt;/p&gt;
&lt;h2&gt;
  
  
  What JSON is and why it matters
&lt;/h2&gt;

&lt;p&gt;JSON stands for JavaScript Object Notation. It was designed to &lt;br&gt;
be lightweight, human-readable, and easy for machines to parse. &lt;br&gt;
It succeeded on all three counts, which is why it became the &lt;br&gt;
default data format for APIs and web services.&lt;/p&gt;

&lt;p&gt;A JSON object looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://devcrate.net"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys are always strings wrapped in double quotes. Values can be &lt;br&gt;
strings, numbers, booleans, arrays, objects, or null. That's it. &lt;br&gt;
The whole spec fits on one page.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why formatting matters
&lt;/h2&gt;

&lt;p&gt;Minified JSON is fast to transmit but impossible to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://devcrate.net"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Formatted JSON is slower to transmit but immediately readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://devcrate.net"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you're debugging an API response, the difference between &lt;br&gt;
those two is the difference between finding the problem in &lt;br&gt;
30 seconds or 10 minutes.&lt;/p&gt;

&lt;p&gt;Always format JSON when you're reading it. Always minify it &lt;br&gt;
when you're sending it in production.&lt;/p&gt;
&lt;h2&gt;
  
  
  The most common JSON mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Trailing commas&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the number one JSON error. It's valid in JavaScript &lt;br&gt;
but illegal in JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the comma after the last item. Every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single quotes instead of double quotes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSON requires double quotes. Single quotes are not valid — &lt;br&gt;
even though JavaScript accepts them just fine. This breaks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;'name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'DevCrate'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Unquoted keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keys must always be strings in double quotes. This is valid &lt;br&gt;
JavaScript but invalid JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Comments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;JSON has no comment syntax. This will throw an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DevCrate"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need comments in a config file, consider YAML instead — &lt;br&gt;
it supports comments and is generally more human-friendly for &lt;br&gt;
configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mismatched brackets&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every { needs a }. Every [ needs a ]. In deeply nested &lt;br&gt;
JSON this is easy to lose track of. A formatter catches this &lt;br&gt;
immediately.&lt;/p&gt;
&lt;h2&gt;
  
  
  Validating JSON
&lt;/h2&gt;

&lt;p&gt;Validation is just asking one question — is this valid JSON &lt;br&gt;
that any parser can read? The answer is binary: yes or no.&lt;/p&gt;

&lt;p&gt;When you paste JSON into a validator, it will either confirm &lt;br&gt;
the structure is correct or point you to the exact line where &lt;br&gt;
something is wrong. This is dramatically faster than reading &lt;br&gt;
through the JSON manually trying to spot the issue.&lt;/p&gt;

&lt;p&gt;Common reasons JSON fails validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trailing commas&lt;/li&gt;
&lt;li&gt;Single quotes&lt;/li&gt;
&lt;li&gt;Missing quotes around keys&lt;/li&gt;
&lt;li&gt;Unescaped special characters in strings&lt;/li&gt;
&lt;li&gt;Missing closing brackets or braces&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Converting JSON to other formats
&lt;/h2&gt;

&lt;p&gt;Sometimes JSON isn't the right format for the job. A few &lt;br&gt;
common conversions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON to YAML&lt;/strong&gt; — YAML is more readable and supports comments, &lt;br&gt;
making it popular for configuration files. Many DevOps tools &lt;br&gt;
prefer YAML over JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON to CSV&lt;/strong&gt; — useful when your JSON contains a flat array &lt;br&gt;
of objects and you want to open the data in a spreadsheet. &lt;br&gt;
Works best with consistent, non-nested structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON to object keys&lt;/strong&gt; — sometimes you just need a list of &lt;br&gt;
all the keys in a JSON object to understand its structure &lt;br&gt;
without reading through all the values.&lt;/p&gt;
&lt;h2&gt;
  
  
  A practical example
&lt;/h2&gt;

&lt;p&gt;Here's a real workflow. You're debugging an API call and the &lt;br&gt;
response comes back as a wall of minified JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"William"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"william@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15T10:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"preferences"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"notifications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste it into the &lt;a href="https://devcrate.net/json/" rel="noopener noreferrer"&gt;DevCrate JSON Transformer&lt;/a&gt;, &lt;br&gt;
hit Format, and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"William"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"william@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-01-15T10:30:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"preferences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"notifications"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can actually read it. The bug that was invisible in the &lt;br&gt;
minified version becomes obvious in seconds.&lt;/p&gt;

&lt;p&gt;Everything runs in your browser. Your API responses, your &lt;br&gt;
internal payloads, your data — none of it touches a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one rule
&lt;/h2&gt;

&lt;p&gt;If you take nothing else from this guide, take this: always &lt;br&gt;
format your JSON before you read it. It costs two seconds and &lt;br&gt;
saves ten minutes. Every time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm William, the developer behind DevCrate. I built the JSON &lt;br&gt;
Transformer because too many formatters send your data to a &lt;br&gt;
server. Yours stays in your browser — always. I'm building &lt;br&gt;
DevCrate from home one tool at a time. My oldest son is &lt;br&gt;
graduating in May and moving on to Officer Candidate School — &lt;br&gt;
couldn't be more proud. My youngest is heading into his undergrad &lt;br&gt;
and every subscriber helps make that a little easier. If this &lt;br&gt;
guide helped you, try the tool — it's free, runs entirely in &lt;br&gt;
your browser, and your data never leaves your machine. And if &lt;br&gt;
there's something missing or something that could work better, &lt;br&gt;
the contact form on the about page comes straight to me.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm always listening.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>json</category>
    </item>
    <item>
      <title>Why I built DevCrate — and what makes it different</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Tue, 24 Mar 2026 03:40:59 +0000</pubDate>
      <link>https://dev.to/willivan0706/why-i-built-devcrate-and-what-makes-it-different-502c</link>
      <guid>https://dev.to/willivan0706/why-i-built-devcrate-and-what-makes-it-different-502c</guid>
      <description>&lt;p&gt;Every developer has a tab problem.&lt;/p&gt;

&lt;p&gt;You're in the middle of debugging an API response and you need to &lt;br&gt;
format some JSON. So you open a new tab. Then you need to decode &lt;br&gt;
a JWT. Another tab. Test a regex. Another tab. Check a cron &lt;br&gt;
expression. Another tab.&lt;/p&gt;

&lt;p&gt;By the time you've solved the problem you have eight tabs open, &lt;br&gt;
you've pasted sensitive data into four different websites you've &lt;br&gt;
never heard of, and you're not entirely sure any of them are &lt;br&gt;
private.&lt;/p&gt;

&lt;p&gt;I've experienced this firsthand.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I built DevCrate
&lt;/h2&gt;

&lt;p&gt;DevCrate is a suite of developer tools that run 100% in your &lt;br&gt;
browser. No server ever sees your data. No login required. No ads. &lt;br&gt;
No dark patterns.&lt;/p&gt;

&lt;p&gt;Everything stays on your machine — your JWT tokens, your SQL &lt;br&gt;
queries, your regex patterns, your API payloads. All of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's live right now
&lt;/h2&gt;

&lt;p&gt;Twelve tools and counting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON Transformer&lt;/strong&gt; — format, minify, convert to YAML or CSV&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT Debugger&lt;/strong&gt; — decode and inspect tokens with claims table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex Studio&lt;/strong&gt; — live match highlighting with flag toggles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL Formatter&lt;/strong&gt; — supports PostgreSQL, MySQL, SQLite, BigQuery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST Client&lt;/strong&gt; — send HTTP requests and inspect responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket Tester&lt;/strong&gt; — connect, send, and inspect frames live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Base64 / Hash&lt;/strong&gt; — encode, decode, MD5, SHA-256, SHA-512&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cron Builder&lt;/strong&gt; — visual editor with human-readable output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL Inspector&lt;/strong&gt; — parse, encode, decode any URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff Tool&lt;/strong&gt; — compare text and JSON line by line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timestamp Converter&lt;/strong&gt; — Unix to date and back, with timezones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML Formatter&lt;/strong&gt; — format and beautify HTML instantly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What makes it different
&lt;/h2&gt;

&lt;p&gt;A lot of developer tool sites exist. Most of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Show ads between every tool interaction&lt;/li&gt;
&lt;li&gt;Require an account to save anything&lt;/li&gt;
&lt;li&gt;Send your data to a server without telling you&lt;/li&gt;
&lt;li&gt;Were built quickly to rank for keywords, not to be genuinely useful&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DevCrate was built the other way around. I started with the tools &lt;br&gt;
I actually use every day and built each one to be genuinely good &lt;br&gt;
at its job — not just good enough to get traffic.&lt;/p&gt;

&lt;p&gt;The privacy-first approach isn't a marketing line. It's a &lt;br&gt;
technical decision baked into how every tool works. There is no &lt;br&gt;
server processing your input. There is no database storing your &lt;br&gt;
queries. The code runs in your browser and nowhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's coming
&lt;/h2&gt;

&lt;p&gt;The public roadmap lives at &lt;a href="https://devcrate.net/roadmap" rel="noopener noreferrer"&gt;devcrate.net/roadmap&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The short version:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Export results as files (Q2 2026)&lt;/li&gt;
&lt;li&gt;Session history so you never lose work (Q2 2026)&lt;/li&gt;
&lt;li&gt;Saved configurations (Q2 2026)&lt;/li&gt;
&lt;li&gt;User accounts and cloud sync (Q3 2026)&lt;/li&gt;
&lt;li&gt;Team workspaces (Q4 2026)&lt;/li&gt;
&lt;li&gt;CLI and API access (Q4 2026)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pricing model
&lt;/h2&gt;

&lt;p&gt;DevCrate is free to start — genuinely free. The free tier gives you full access to all twelve tools with reasonable usage limits.&lt;/p&gt;

&lt;p&gt;Pro is $19/month for power users who need higher limits, export, and history. Team is $49/month for when we build out the &lt;br&gt;
collaboration features.&lt;/p&gt;

&lt;p&gt;No surprise upsells. No modals at 2am telling you your trial &lt;br&gt;
expired.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;devcrate.net&lt;/a&gt; — no signup, no card, just &lt;br&gt;
open it and use it.&lt;/p&gt;

&lt;p&gt;If something's broken, missing, or could work better — the &lt;br&gt;
contact form on the about page comes straight to me.&lt;/p&gt;

&lt;p&gt;I truly read every message.&lt;/p&gt;




&lt;h2&gt;
  
  
  Something about me
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;I'm William, the developer behind DevCrate. I'm building &lt;br&gt;
this from home one tool at a time. I started this project partly &lt;br&gt;
because tab switching is a frustrating way to get work done, and partly because my youngest son is heading into his undergrad and every &lt;br&gt;
subscriber helps make that a little easier. If you found this &lt;br&gt;
useful, try a tool and tell me what's missing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm always listening.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tooling</category>
      <category>productivity</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
