<?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>Why I removed the AI behind my dev tools (and kept the features that earned it)</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 03 Jun 2026 00:51:35 +0000</pubDate>
      <link>https://dev.to/willivan0706/why-i-removed-the-ai-behind-my-dev-tools-and-kept-the-features-that-earned-it-2c6j</link>
      <guid>https://dev.to/willivan0706/why-i-removed-the-ai-behind-my-dev-tools-and-kept-the-features-that-earned-it-2c6j</guid>
      <description>&lt;p&gt;I spent a week adding AI features to my developer tool suite. Natural language to regex. AI-powered SQL query explanations. The kind of features you'd expect in a 2026 product. They worked. Users could finally describe what they wanted in plain English and get a real answer back.&lt;/p&gt;

&lt;p&gt;Then I ripped the AI out from under them.&lt;/p&gt;

&lt;p&gt;Not the features themselves — some of them are still there, still labeled with the same Pro badge, still doing the same thing from the user's perspective. What changed is what runs behind the input box. The API calls to a remote LLM are gone. The features that survived are now powered by a few hundred lines of client-side pattern matching, and the features that couldn't survive that change are gone entirely.&lt;/p&gt;

&lt;p&gt;This is the story of how that decision happened, why I think more solo developers should be making the same call, and the simple framework I now use before adding any new feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://devcrate.net" rel="noopener noreferrer"&gt;DevCrate&lt;/a&gt; — a suite of 22 browser-based developer tools. JSON formatter, JWT decoder, regex tester, cron builder, that kind of thing. The whole product has one promise: &lt;strong&gt;nothing leaves your browser&lt;/strong&gt;. No accounts, no tracking, no data sent anywhere. You can paste production secrets into the JWT decoder and nothing will phone home.&lt;/p&gt;

&lt;p&gt;That promise isn't a marketing line. It's the entire reason the product exists. Every other online dev tool sends your data to their server to process. Most developers don't think about it. The ones who do, want an alternative. That's me, that's my users.&lt;/p&gt;

&lt;p&gt;For two months things were going well. Tools were shipping, search traffic was climbing, the project was actually starting to feel like a product. Then I had what felt like a great idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "great" idea
&lt;/h2&gt;

&lt;p&gt;The regex tool was popular but I could see the friction. People landed on it knowing what they wanted to match but not how to write it. "Match email addresses but not the ones with plus signs in them." "Find lines starting with a number followed by a period." Stuff that's trivial to describe and annoying to write as regex.&lt;/p&gt;

&lt;p&gt;Solution: add a natural language input. Type the description, get the pattern. Powered by an API call to an LLM behind the scenes. Pro feature. Easy upsell.&lt;/p&gt;

&lt;p&gt;Same logic applied to SQL — paste a query, get a plain English explanation. Inherited a 300-line query from a former coworker? Now you understand what it does in 10 seconds. Pro feature, easy upsell.&lt;/p&gt;

&lt;p&gt;I built both. They worked. I shipped them. I felt clever.&lt;/p&gt;




&lt;h2&gt;
  
  
  The realization
&lt;/h2&gt;

&lt;p&gt;A few days after shipping, I was testing the regex tool in the browser and noticed the network tab. The natural-language input was firing a request to &lt;code&gt;api.anthropic.com&lt;/code&gt; — failing on CORS, technically, but trying. Every keystroke that triggered the "Generate" button was reaching out across the public internet with whatever the user had typed into the box.&lt;/p&gt;

&lt;p&gt;The CORS failure was almost beside the point. The architecture was the problem. I had built a feature that, by design, took user input and sent it to a third party. The fact that it didn't currently succeed because the browser was blocking it was a security accident, not a security feature. The intended path was for that data to leave the machine.&lt;/p&gt;

&lt;p&gt;I sat with that for a minute. I had two ways to think about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt; "Fix" the CORS issue by routing the request through my own Cloudflare Worker. Now the request succeeds. The user's regex description goes to my worker, which forwards it to the LLM, which sends back a pattern. Technically my homepage still says "nothing leaves your browser" because, well, it's &lt;em&gt;only&lt;/em&gt; the Pro AI features that send anything anywhere. Everything else is still client-side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B:&lt;/strong&gt; Notice that the sentence I just wrote contains the word "only" doing a lot of load-bearing work, and that this is exactly how every product that betrays its users' trust talks about itself.&lt;/p&gt;

&lt;p&gt;"Only the Pro features." "Only when you opt in." "Only the data we need." "Only to improve the service." Every analytics SDK is "opt-in." Every tracker is "for your benefit." Every data collection feature has a justification that sounds reasonable until you stack them up next to each other.&lt;/p&gt;

&lt;p&gt;The whole reason DevCrate exists is that I got tired of products doing that. And here I was, doing that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually did
&lt;/h2&gt;

&lt;p&gt;Here's the part where most blog posts oversimplify, so I want to be specific. I didn't remove the natural-language regex feature. I removed the AI behind it. There's a difference and it matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What got deleted entirely:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI SQL query explainer. The whole feature is gone — there's no way to take an arbitrary SQL query and explain it without running it through a model, and I wasn't willing to do that. So that feature died.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What got rewired but kept:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The regex tool's natural-language input.&lt;/strong&gt; Same input box, same Pro badge, same "Generate →" button. But behind the button is now a function with about 30 hardcoded patterns covering the most common requests (email, phone, UUID, hex color, IPv4, "lines starting with a number," and so on). If your description matches one of them, you get the regex instantly. If it doesn't, you get a message explaining what phrases are supported, plus a link to my contact form pre-filled with what you tried — so I can grow the pattern list based on what real users are actually asking for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cron tool's natural-language input.&lt;/strong&gt; Same treatment. About 15 hardcoded patterns covering common schedules ("every weekday at 9am," "every 15 minutes," "first day of month"). No API call. If it doesn't match, you get a "suggest a pattern" link.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the user's perspective, the regex and cron features still work for the common cases. The Pro badge is still there. The pitch is still "describe what you want, get the pattern." What's different is that &lt;strong&gt;nothing — not a single byte — leaves your browser when you use them&lt;/strong&gt;. The features got dumber. The product promise got honest again.&lt;/p&gt;

&lt;p&gt;I think this distinction is more useful than the version where I claim I "removed AI." A lot of AI features could be replaced with deterministic logic this way. The question isn't whether AI exists in your feature, it's whether AI is doing work a hardcoded function couldn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The framework I use now
&lt;/h2&gt;

&lt;p&gt;This wasn't a one-off. Every product has a core promise — the thing you say to yourself and your users that defines what the product &lt;em&gt;is&lt;/em&gt;. Privacy-first. Open source. No subscription. Beginner-friendly. Lightning-fast. Whatever it is, your features either reinforce that promise or they erode it.&lt;/p&gt;

&lt;p&gt;Before I add any new feature now, I ask three questions:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Does this feature still work if I'm honest about what it does?
&lt;/h3&gt;

&lt;p&gt;The test: write the marketing copy for the feature in the most boring, technical, accurate way possible. No spin. If "AI regex generator" becomes "we send your description to a third-party LLM provider and they return a regex pattern" and that statement contradicts something else on my homepage, the feature has a problem. Not necessarily a fatal one — but a problem worth solving before launch.&lt;/p&gt;

&lt;p&gt;For the SQL explainer, the honest description was "we send your query to a third-party LLM." That broke the homepage promise. The feature died.&lt;/p&gt;

&lt;p&gt;For the regex natural-language input, the honest description became "matches your description against a list of common patterns in your browser." That works fine. The feature lived.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. What's the dumbest version of this feature that would still be useful?
&lt;/h3&gt;

&lt;p&gt;The AI regex generator was an LLM call. The non-AI version was a 50-line function with hardcoded patterns. The dumb version covered 80% of real user requests, ran instantly, cost nothing, and didn't send a single byte off the user's machine. It wasn't as impressive. It was better for the product.&lt;/p&gt;

&lt;p&gt;I'm not anti-AI. I use AI tools every day to build DevCrate. The question isn't whether AI is useful — it's whether &lt;em&gt;this particular feature&lt;/em&gt; needs AI to deliver the value, or whether AI is just the impressive-sounding way to ship it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Who benefits if I add this — me or the user?
&lt;/h3&gt;

&lt;p&gt;This one stings. When I shipped the AI features I had a clear story: Pro upsell, modern feature set, market signal that the product is "real." Those are all reasons that benefit &lt;em&gt;me&lt;/em&gt;. The user got a feature that mostly already existed in the form of asking ChatGPT directly — except now they were paying me for it.&lt;/p&gt;

&lt;p&gt;Features that primarily benefit you (the developer) and only secondarily benefit the user have a way of eroding trust quietly. Users can feel it even when they can't articulate it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'm not saying
&lt;/h2&gt;

&lt;p&gt;I'm not saying don't add AI to your product. Many products genuinely benefit from AI features — that's why the entire industry is moving in that direction. Cursor, GitHub Copilot, Linear's AI features, Claude itself. All of them ship AI that earns its place.&lt;/p&gt;

&lt;p&gt;I'm saying: &lt;strong&gt;be honest about whether your specific feature does&lt;/strong&gt;. For a privacy-first browser tool, sending data to a third-party LLM was wrong — even when it was opt-in, even when it was a paid feature, even when the homepage was technically still accurate. For a code editor running on your local machine, calling out to an LLM is fine. The category matters more than the trend.&lt;/p&gt;

&lt;p&gt;I'm also not saying I'd never bring real AI back to DevCrate. If I built a feature where the model ran entirely client-side — using something like &lt;a href="https://webllm.mlc.ai/" rel="noopener noreferrer"&gt;WebLLM&lt;/a&gt; or a small WASM model — that would honor the privacy promise. The technology exists. It's not ready for the use cases I had in mind yet, but it's getting closer. When it's ready, that's a feature worth shipping.&lt;/p&gt;




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

&lt;p&gt;The best moment to catch a feature that contradicts your product is before any user complains about it. Not because users won't notice — they will, eventually — but because by then the trust is already gone. The CORS error that flagged this for me was lucky timing. I'd rather build the kind of habit where I don't need lucky timing.&lt;/p&gt;

&lt;p&gt;The version of the lesson I'm taking forward isn't "remove features." It's: &lt;strong&gt;separate the feature from the implementation&lt;/strong&gt;. The AI was the implementation. The pitch was the feature. Some pitches can be kept by swapping the implementation. Some can't. Knowing which is which is most of the work.&lt;/p&gt;

&lt;p&gt;The next time you find yourself adding a feature that requires a footnote to explain why it doesn't contradict your product's core promise, stop. Write the footnote. Read it out loud. Decide if you'd believe yourself.&lt;/p&gt;

&lt;p&gt;If you wouldn't, your users won't either.&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;. This is the kind of thing I'd love to talk through with other solo devs — if you've shipped a feature that you later realized fought with your product's core promise, I genuinely want to hear how you handled it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What feature have you removed (or wished you'd removed) from your product? Drop it in the comments — I read every one.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>indiehackers</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Base64 explained — what it is, when to use it, and the gotchas that bite developers</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Wed, 27 May 2026 19:50:53 +0000</pubDate>
      <link>https://dev.to/willivan0706/base64-explained-what-it-is-when-to-use-it-and-the-gotchas-that-bite-developers-p8p</link>
      <guid>https://dev.to/willivan0706/base64-explained-what-it-is-when-to-use-it-and-the-gotchas-that-bite-developers-p8p</guid>
      <description>&lt;p&gt;You see a long string of letters and numbers ending in &lt;code&gt;==&lt;/code&gt; and wonder what it is. You paste a JWT into a tool and the middle section is mostly readable. You embed an image in an HTML email and the &lt;code&gt;src&lt;/code&gt; attribute is a wall of characters. You upload a PDF to an API and the docs tell you to "send it as Base64." They're all the same encoding, and most developers use it without ever really understanding what it does.&lt;/p&gt;

&lt;p&gt;This guide covers what Base64 actually is, when you should reach for it, the common mistakes (including the biggest one — assuming it's encryption), URL-safe variants, padding rules, and how to encode and decode it in every major language.&lt;/p&gt;

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

&lt;p&gt;Base64 is an encoding that converts binary data into ASCII text using 64 specific characters: &lt;code&gt;A-Z&lt;/code&gt;, &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt;, plus &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt;. The &lt;code&gt;=&lt;/code&gt; character is used for padding at the end. Every 3 bytes of input become exactly 4 Base64 characters of output — meaning Base64 increases data size by roughly 33%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input bytes:   "Hi"                  (2 bytes: 0x48 0x69)
Binary:        01001000 01101001
Group in 6s:   010010 000110 1001(00)   ← last group padded with zeros
Base64 chars:  S      G      k    =     ← '=' = padding

Result:        "SGk="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The math: 64 characters means each character represents 6 bits. The lowest common multiple of 6 bits (one Base64 char) and 8 bits (one byte) is 24 bits — which is 3 bytes or 4 Base64 chars. That's why Base64 always works in groups of 4 output characters, and why padding exists at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The biggest misconception: Base64 is not encryption
&lt;/h2&gt;

&lt;p&gt;This catches developers and non-developers alike. Base64 looks like gibberish, so it feels like a secret. It isn't. Anyone can decode Base64 instantly — there's no key, no password, no algorithm to crack. It's a transparent transformation, like writing in a different alphabet.&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;// "Encrypted" password?&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cGFzc3dvcmQxMjM=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Decoded in one line&lt;/span&gt;
&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cGFzc3dvcmQxMjM=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// → "password123"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base64 is encoding, not encryption. Use it to &lt;em&gt;transport&lt;/em&gt; data safely through text-only channels — never to &lt;em&gt;hide&lt;/em&gt; data. If you need confidentiality, use real encryption: AES, NaCl/libsodium, or TLS for data in transit.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to use Base64
&lt;/h2&gt;

&lt;p&gt;Base64 exists to move binary data through systems that expect text. The most common cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedding binary in JSON or XML&lt;/strong&gt; — neither format supports raw bytes. APIs that accept file uploads as part of a JSON payload use Base64 to represent the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data URLs in HTML/CSS&lt;/strong&gt; — &lt;code&gt;data:image/png;base64,iVBORw0KGgo...&lt;/code&gt; lets you embed an image inline without a separate HTTP request. Useful for small icons and email signatures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP Basic Auth&lt;/strong&gt; — the &lt;code&gt;Authorization&lt;/code&gt; header sends credentials as &lt;code&gt;Basic &amp;lt;base64-of-username:password&amp;gt;&lt;/code&gt;. This is also a perfect example of why Base64 isn't encryption — Basic Auth is only secure when paired with HTTPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JWTs&lt;/strong&gt; — JSON Web Tokens consist of three Base64URL-encoded sections separated by dots. The header and payload are readable JSON; only the signature is opaque.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email attachments&lt;/strong&gt; — SMTP is technically a 7-bit text protocol, so attachments have been Base64-encoded by default since the MIME standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cryptographic keys and certificates&lt;/strong&gt; — PEM files (the &lt;code&gt;-----BEGIN CERTIFICATE-----&lt;/code&gt; blocks) wrap Base64-encoded binary keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  Padding — why some Base64 ends in = and some doesn't
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;=&lt;/code&gt; at the end of Base64 strings is padding. It exists because Base64 works in groups of 3 input bytes, and not every input is a multiple of 3 bytes long. When the input is short by 1 or 2 bytes, the encoder pads the output with &lt;code&gt;=&lt;/code&gt; characters so the result is always a multiple of 4 characters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input length (mod 3)   Padding   Example
─────────────────────────────────────────
0 (multiple of 3)      none      "Man"  → "TWFu"
1                      ==        "M"    → "TQ=="
2                      =         "Ma"   → "TWE="
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some encoders and protocols strip the padding to save bytes. JWTs do this — the Base64URL encoding inside a JWT has no padding at all. If you're manually decoding Base64 from a JWT, you may need to add the padding back before some decoders will accept it.&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;// JavaScript: add padding back to an unpadded Base64 string&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64&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;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b64&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;remainder&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;b64&lt;/span&gt; &lt;span class="o"&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;repeat&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;remainder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b64&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;
  
  
  Base64 vs Base64URL — the variant that matters for the web
&lt;/h2&gt;

&lt;p&gt;Standard Base64 uses &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; as its 62nd and 63rd characters. Both have special meaning in URLs: &lt;code&gt;+&lt;/code&gt; means "space" in query strings, and &lt;code&gt;/&lt;/code&gt; is a path separator. Putting standard Base64 in a URL without further encoding breaks things.&lt;/p&gt;

&lt;p&gt;Base64URL (defined in RFC 4648) solves this by swapping those characters: &lt;code&gt;+&lt;/code&gt; becomes &lt;code&gt;-&lt;/code&gt;, and &lt;code&gt;/&lt;/code&gt; becomes &lt;code&gt;_&lt;/code&gt;. It also typically omits padding. The result is safe to drop directly into URLs, filenames, and HTTP headers.&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="nx"&gt;Standard&lt;/span&gt; &lt;span class="nx"&gt;Base64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc/d+ef==&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="nx"&gt;Base64URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;abc_d-ef&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Convert one to the other&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toUrlSafe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b64&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;\+&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;/&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="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="p"&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;fromUrlSafe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64u&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;b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b64u&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// add padding back&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JWTs use Base64URL. So do most modern token formats, OAuth state parameters, and anything else that travels in a URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  Encoding and decoding in JavaScript
&lt;/h2&gt;

&lt;p&gt;JavaScript has two built-in functions: &lt;code&gt;btoa()&lt;/code&gt; (binary-to-ASCII, encode) and &lt;code&gt;atob()&lt;/code&gt; (ASCII-to-binary, decode). The names are confusing — they don't work the way you'd expect for arbitrary binary data.&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;// Simple ASCII strings — these work&lt;/span&gt;
&lt;span class="nf"&gt;btoa&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!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → "SGVsbG8sIHdvcmxkIQ=="&lt;/span&gt;

&lt;span class="nf"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SGVsbG8sIHdvcmxkIQ==&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="c1"&gt;// Unicode strings — this BREAKS&lt;/span&gt;
&lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;héllo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → DOMException: invalid character&lt;/span&gt;

&lt;span class="c1"&gt;// Correct way for Unicode: encode to UTF-8 first&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;utf8ToBase64&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="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;unescape&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;str&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;base64ToUtf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b64&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;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;escape&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;b64&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;utf8ToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;héllo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// → "aMOpbGxv"&lt;/span&gt;
&lt;span class="nf"&gt;base64ToUtf8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aMOpbGxv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → "héllo"&lt;/span&gt;

&lt;span class="c1"&gt;// Modern alternative (Node 16+, modern browsers)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&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;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;héllo&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;b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For binary data like file uploads, work with &lt;code&gt;ArrayBuffer&lt;/code&gt; or &lt;code&gt;Uint8Array&lt;/code&gt;:&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;// Convert a file to Base64 (browser)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fileToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;bytes&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;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&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;for &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;byte&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;binary&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;byte&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;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// FileReader alternative — gives you a data URL&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fileToDataUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;reader&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;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&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;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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;
  
  
  Encoding and decoding in Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="c1"&gt;# Encode a string
&lt;/span&gt;&lt;span class="n"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&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;# → b"SGVsbG8sIHdvcmxkIQ=="
&lt;/span&gt;
&lt;span class="c1"&gt;# Decode
&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SGVsbG8sIHdvcmxkIQ==&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → b"Hello, world!"
&lt;/span&gt;
&lt;span class="c1"&gt;# URL-safe variant — for JWTs, URLs, filenames
&lt;/span&gt;&lt;span class="n"&gt;url_safe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlsafe_b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data with /and+chars&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → b"ZGF0YSB3aXRoIC9hbmQrY2hhcnM="
&lt;/span&gt;
&lt;span class="c1"&gt;# Encode a file
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;photo.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;encoded_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ascii&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Encoding and decoding on the command line
&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;# macOS and Linux — encode&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"Hello, world!"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt;
&lt;span class="c"&gt;# → SGVsbG8sIHdvcmxkIQ==&lt;/span&gt;

&lt;span class="c"&gt;# Decode&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"SGVsbG8sIHdvcmxkIQ=="&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="c"&gt;# → Hello, world!&lt;/span&gt;

&lt;span class="c"&gt;# Encode a file&lt;/span&gt;
&lt;span class="nb"&gt;base64 &lt;/span&gt;photo.jpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; photo.txt

&lt;span class="c"&gt;# Decode a file&lt;/span&gt;
&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; photo.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; photo.jpg

&lt;span class="c"&gt;# Watch out — many systems wrap output at 76 characters by default&lt;/span&gt;
&lt;span class="c"&gt;# Use -w 0 (GNU) or no flag (BSD) to disable wrapping&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"long content..."&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Encoding and decoding in SQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello, world!'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;bytea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → SGVsbG8sIHdvcmxkIQ==&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SGVsbG8sIHdvcmxkIQ=='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'base64'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → Hello, world!&lt;/span&gt;

&lt;span class="c1"&gt;-- MySQL 8+&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;TO_BASE64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hello, world!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → SGVsbG8sIHdvcmxkIQ==&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;FROM_BASE64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SGVsbG8sIHdvcmxkIQ=='&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;h2&gt;
  
  
  Common bugs and how to avoid them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The line-wrap trap.&lt;/strong&gt; Some implementations (notably MIME and OpenSSL) wrap Base64 output at 64 or 76 characters with newlines. Other implementations reject input that contains newlines. If you're seeing "invalid character" errors decoding what looks like valid Base64, strip whitespace first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The padding mismatch.&lt;/strong&gt; JWTs and URL-safe Base64 typically omit padding. Many decoders require it. If decoding fails, calculate how many &lt;code&gt;=&lt;/code&gt; characters to add: &lt;code&gt;(4 - (length % 4)) % 4&lt;/code&gt; of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UTF-8 assumption.&lt;/strong&gt; Base64 encodes bytes, not characters. Encoding a string assumes you know what character encoding it's in. Always make the encoding explicit (UTF-8 is almost always the right answer) and decode back to bytes before treating the result as a string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The size surprise.&lt;/strong&gt; Base64 increases payload size by 33%. For small assets it doesn't matter. For a 10 MB file embedded in JSON, it does — you're sending 13.3 MB over the wire. For larger files, prefer multipart uploads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating it as a secret.&lt;/strong&gt; Worth saying twice: Base64 is not encryption. Don't store passwords, API keys, or other sensitive data as Base64 expecting it to be hidden. If you can see the encoded string, you can see the original.&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 &lt;a href="https://devcrate.net/base64/" rel="noopener noreferrer"&gt;Base64 tool&lt;/a&gt; exists because I got tired of dropping pseudo-random strings into shady online converters to see what was inside. It encodes and decodes both standard and URL-safe variants, handles files, and never sends a single byte off your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If there's a Base64 case this guide didn't cover, drop it in the comments — I read every one.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>base64</category>
      <category>javascript</category>
      <category>security</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Unix timestamps explained — converting, formatting, and avoiding the common mistakes</title>
      <dc:creator>William Andrews</dc:creator>
      <pubDate>Mon, 11 May 2026 00:01:00 +0000</pubDate>
      <link>https://dev.to/willivan0706/unix-timestamps-explained-converting-formatting-and-avoiding-the-common-mistakes-2ei2</link>
      <guid>https://dev.to/willivan0706/unix-timestamps-explained-converting-formatting-and-avoiding-the-common-mistakes-2ei2</guid>
      <description>&lt;p&gt;You're reading a database record and there's a column called &lt;code&gt;created_at&lt;/code&gt; with a value of &lt;code&gt;1746835200&lt;/code&gt;. Or you're debugging an API response and the timestamp field contains &lt;code&gt;1746835200000&lt;/code&gt;. Or your log file shows &lt;code&gt;1746835200&lt;/code&gt; and you need to know when that actually happened.&lt;/p&gt;

&lt;p&gt;This guide covers everything you need to work with Unix timestamps confidently — what they are, how to convert them in every major language and tool, the timezone trap that catches most developers, the milliseconds-vs-seconds mistake that causes the worst bugs, and a few things worth knowing about where timestamp math breaks down.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a Unix timestamp actually is
&lt;/h2&gt;

&lt;p&gt;A Unix timestamp is the number of seconds that have elapsed since January 1, 1970 at 00:00:00 UTC. That moment — midnight UTC on January 1st, 1970 — is called the Unix epoch. Every second that passes increments every Unix timestamp by 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0           → 1970-01-01 00:00:00 UTC
1000000000  → 2001-09-09 01:46:40 UTC
1700000000  → 2023-11-14 22:13:20 UTC
1746835200  → 2025-05-10 00:00:00 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things this definition makes clear: timestamps are always UTC — they don't carry timezone information. They're always an integer (or occasionally a float with sub-second precision). And they're always positive for dates after 1970, negative for dates before.&lt;/p&gt;

&lt;p&gt;The name "Unix timestamp" is used interchangeably with "epoch time", "POSIX time", and "Unix time". They all mean the same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Seconds vs milliseconds — the bug that's bitten everyone
&lt;/h2&gt;

&lt;p&gt;This is the single most common timestamp bug. JavaScript's &lt;code&gt;Date.now()&lt;/code&gt; returns milliseconds since the epoch. Most Unix utilities and databases use seconds. The difference is a factor of 1000 — if you pass a millisecond timestamp where seconds are expected you get a date ~33,000 years in the future, and if you pass seconds where milliseconds are expected you get a date in early 1970.&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;// JavaScript — always milliseconds&lt;/span&gt;
&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;              &lt;span class="c1"&gt;// 1746835200000 (13 digits)&lt;/span&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="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="c1"&gt;// 1746835200000 (13 digits)&lt;/span&gt;

&lt;span class="c1"&gt;// To get seconds in JavaScript&lt;/span&gt;
&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;// 1746835200 (10 digits)&lt;/span&gt;

&lt;span class="c1"&gt;// Quick check: is this seconds or milliseconds?&lt;/span&gt;
&lt;span class="c1"&gt;// 10 digits → seconds (valid until 2286)&lt;/span&gt;
&lt;span class="c1"&gt;// 13 digits → milliseconds&lt;/span&gt;
&lt;span class="c1"&gt;// 16 digits → microseconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The digit count rule is the fastest way to tell which you're dealing with. A 10-digit timestamp is seconds. A 13-digit timestamp is milliseconds. When you see something in between — 11 or 12 digits — something has gone wrong upstream.&lt;/p&gt;




&lt;h2&gt;
  
  
  Converting timestamps in JavaScript
&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;// Current timestamp&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nowMs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&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;nowSec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;// Timestamp to Date object&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&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="c1"&gt;// seconds → multiply by 1000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateFromMs&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// already milliseconds&lt;/span&gt;

&lt;span class="c1"&gt;// Date to timestamp&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-05-10&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;// Format a timestamp as a readable string&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;second&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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;ts&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="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;formatTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UTC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → "May 10, 2025, 00:00:00"&lt;/span&gt;

&lt;span class="nf"&gt;formatTimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// → "May 09, 2025, 20:00:00"  ← note: different day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Converting timestamps in Python
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="c1"&gt;# Current timestamp
&lt;/span&gt;&lt;span class="n"&gt;ts_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;ts_float&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# with sub-second precision
&lt;/span&gt;
&lt;span class="c1"&gt;# Timestamp to datetime (UTC)
&lt;/span&gt;&lt;span class="n"&gt;dt_utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# → datetime(2025, 5, 10, 0, 0, tzinfo=timezone.utc)
&lt;/span&gt;
&lt;span class="c1"&gt;# Datetime to timestamp
&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# → 1746835200
&lt;/span&gt;
&lt;span class="c1"&gt;# Format
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt_utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%Y-%m-%d %H:%M:%S %Z&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# → "2025-05-10 00:00:00 UTC"
&lt;/span&gt;
&lt;span class="c1"&gt;# Arithmetic
&lt;/span&gt;&lt;span class="n"&gt;one_day&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&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="n"&gt;tomorrow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt_utc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;one_day&lt;/span&gt;
&lt;span class="n"&gt;ts_tomorrow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Converting timestamps in SQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;to_timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → 2025-05-10 00:00:00+00&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- → current Unix timestamp&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;to_timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'America/New_York'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- → 2025-05-09 20:00:00&lt;/span&gt;

&lt;span class="c1"&gt;-- MySQL&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;FROM_UNIXTIME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → 2025-05-10 00:00:00  (uses server timezone — be careful)&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;UNIX_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;-- → current Unix timestamp&lt;/span&gt;

&lt;span class="c1"&gt;-- SQLite&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'unixepoch'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → 2025-05-10 00:00:00&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'%s'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'now'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- → current Unix timestamp as string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Converting timestamps on the command line
&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;# macOS (BSD date)&lt;/span&gt;
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 1746835200
&lt;span class="c"&gt;# → Sat May 10 00:00:00 UTC 2025&lt;/span&gt;

&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; 1746835200 &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;
&lt;span class="c"&gt;# → 2025-05-10 00:00:00&lt;/span&gt;

&lt;span class="c"&gt;# Linux (GNU date)&lt;/span&gt;
&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; @1746835200
&lt;span class="c"&gt;# → Sat May 10 00:00:00 UTC 2025&lt;/span&gt;

&lt;span class="c"&gt;# Get current timestamp&lt;/span&gt;
&lt;span class="nb"&gt;date&lt;/span&gt; +%s

&lt;span class="c"&gt;# Python one-liner (works everywhere)&lt;/span&gt;
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import datetime; print(datetime.datetime.fromtimestamp(1746835200, tz=datetime.timezone.utc))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The timezone trap
&lt;/h2&gt;

&lt;p&gt;Unix timestamps are always UTC. They contain no timezone information — they're just a count of seconds. The timezone only matters when you convert a timestamp into a human-readable date. This is where most timestamp bugs live.&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;// This timestamp is midnight UTC on May 10th&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// In UTC&lt;/span&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;ts&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;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// → "2025-05-10T00:00:00.000Z"  ← May 10th&lt;/span&gt;

&lt;span class="c1"&gt;// In New York (UTC-4 during EDT)&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dateStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&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;ts&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="c1"&gt;// → "Friday, May 9, 2025"  ← May 9th — different day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule: always be explicit about timezone when converting timestamps to dates. Never rely on the server's local timezone for anything that will be displayed to users or compared across systems. Store timestamps as UTC, convert to user timezone only at the display layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storing timestamps in databases
&lt;/h2&gt;

&lt;p&gt;Two common approaches — integer storage vs native datetime types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integer storage&lt;/strong&gt; — simple, portable, no timezone ambiguity, easy arithmetic. Not human-readable in the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native datetime with timezone&lt;/strong&gt; — &lt;code&gt;TIMESTAMPTZ&lt;/code&gt; in PostgreSQL, &lt;code&gt;DATETIME&lt;/code&gt; with explicit UTC in MySQL. Human-readable, supports native date functions. PostgreSQL's &lt;code&gt;TIMESTAMPTZ&lt;/code&gt; stores in UTC and converts on output — almost always the right choice.&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;-- PostgreSQL: always use TIMESTAMPTZ, not TIMESTAMP&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="c1"&gt;-- TIMESTAMP (without timezone) stores the value as-is with no timezone context&lt;/span&gt;
  &lt;span class="c1"&gt;-- This is almost always wrong&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Query by date range (PostgreSQL handles timezone conversion)&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;events&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="s1"&gt;'2025-05-10'&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'America/New_York'&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;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2025-05-11'&lt;/span&gt; &lt;span class="k"&gt;AT&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'America/New_York'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Timestamp arithmetic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;oneHour&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&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;oneDay&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;86400&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;oneWeek&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;604800&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;ts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1746835200&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;tomorrow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;oneDay&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;nextWeek&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;oneWeek&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Difference between two timestamps&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1746835200&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;end&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1746921600&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;diffDays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 1.0&lt;/span&gt;

&lt;span class="c1"&gt;// Is this timestamp in the past?&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&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="c1"&gt;// Is this timestamp within the last 24 hours?&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isRecent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&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="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to be careful with: "add one month" is ambiguous. Adding 2,592,000 seconds (30 days) to January 31st gives you March 2nd, not February 28th. If calendar-accurate month arithmetic matters, use a library like date-fns or Temporal rather than raw seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  ISO 8601 — the timestamp format you should be using in APIs
&lt;/h2&gt;

&lt;p&gt;When exchanging timestamps between systems, don't use raw Unix timestamps. Use ISO 8601 with an explicit timezone offset.&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;// Bad — ambiguous&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1746835200&lt;/span&gt;

&lt;span class="c1"&gt;// Better — but still requires knowing it's seconds vs milliseconds&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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;2025-05-10 00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// Best — ISO 8601 with UTC offset, unambiguous&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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;2025-05-10T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;created_at&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;2025-05-09T20:00:00-04:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;// same moment, NY local time&lt;/span&gt;

&lt;span class="c1"&gt;// JavaScript&lt;/span&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="mi"&gt;1746835200&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;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// → "2025-05-10T00:00:00.000Z"&lt;/span&gt;

&lt;span class="c1"&gt;// Python&lt;/span&gt;
&lt;span class="nx"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromtimestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1746835200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// → "2025-05-10T00:00:00+00:00"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Y2K38 problem
&lt;/h2&gt;

&lt;p&gt;Unix timestamps stored as 32-bit signed integers will overflow on January 19, 2038 at 03:14:07 UTC. At that moment the value exceeds the maximum positive 32-bit integer (2,147,483,647) and wraps to a large negative number — interpreted as a date in 1901.&lt;/p&gt;

&lt;p&gt;Most modern systems use 64-bit integers, which won't overflow for approximately 292 billion years. If you're working with legacy systems or embedded hardware that stores timestamps as 32-bit values, this is worth checking. New code should always use 64-bit integers.&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;// 32-bit max timestamp&lt;/span&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="mi"&gt;2147483647&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;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// → "2038-01-19T03:14:07.000Z"&lt;/span&gt;

&lt;span class="c1"&gt;// One second later in a 32-bit signed system: wraps to -2147483648&lt;/span&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2147483648&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;toISOString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// → "1901-12-13T20:45:52.000Z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;If you need to convert a timestamp quickly, &lt;a href="https://devcrate.net/timestamp/" rel="noopener noreferrer"&gt;DevCrate's Timestamp Converter&lt;/a&gt; handles seconds and milliseconds automatically, lets you pick any timezone, and converts in both directions — all in the browser with nothing sent anywhere.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <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>
  </channel>
</rss>
