<?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: Sujal Khatri</title>
    <description>The latest articles on DEV Community by Sujal Khatri (@sujalkhatri).</description>
    <link>https://dev.to/sujalkhatri</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4185212%2F5f3e44c8-2409-4771-89d7-4043348e95cb.jpg</url>
      <title>DEV Community: Sujal Khatri</title>
      <link>https://dev.to/sujalkhatri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sujalkhatri"/>
    <language>en</language>
    <item>
      <title>Base64 Encode and Decode Without Breaking Emoji and Non-English Text</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Wed, 28 Oct 2026 05:23:05 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/base64-encode-and-decode-without-breaking-emoji-and-non-english-text-24nj</link>
      <guid>https://dev.to/sujalkhatri/base64-encode-and-decode-without-breaking-emoji-and-non-english-text-24nj</guid>
      <description>&lt;p&gt;You have probably met Base64 in the wild: an API token, a data URI in an image tag, a long string pasted into a config file, or an email attachment. It looks like random letters, numbers, + and /, often ending in =.&lt;/p&gt;

&lt;p&gt;Base64 is not magic. It is a way to represent any bytes as plain, safe text characters so they survive places that only handle text — URLs, JSON, emails, config files. Every 3 bytes become 4 characters, which is why encoded output is about 33% larger than the input.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: emoji and non-English text break naive encoding
&lt;/h2&gt;

&lt;p&gt;In the browser, the classic functions are btoa() and atob(). They only handle Latin-1 characters. Feed them an emoji or Hindi, Arabic, Chinese text and they throw an error or corrupt the result.&lt;/p&gt;

&lt;p&gt;The fix is to encode the text as UTF-8 first, then Base64-encode those bytes. A Unicode-safe way in modern JavaScript:&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;// Encode, Unicode-safe&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;Hello 🌍&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;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;bytes&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;encoded&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="nx"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// SGVsbG8g8J+MjQ=&lt;/span&gt;

&lt;span class="c1"&gt;// Decode, Unicode-safe&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&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;encoded&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoded&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;TextDecoder&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;decodedBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hello 🌍&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you just need the answer once, you do not need to write this code or install anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encode or decode in seconds, without installing anything
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Paste your text and click Encode, or paste a Base64 string and click Decode.&lt;/li&gt;
&lt;li&gt;Invalid Base64 shows a clear error instead of garbage output — that matters when you are debugging a token and cannot tell whether the string itself is broken.&lt;/li&gt;
&lt;li&gt;Copy the result with one click.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Try it here: &lt;a href="https://bulktextkit.gt.tc/tools/base64-encoder-decoder.php?utm_source=devto" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc/tools/base64-encoder-decoder.php?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It runs entirely in your browser, so the text you paste never leaves your device — useful exactly when the string you are decoding is a real token.&lt;/p&gt;

&lt;h2&gt;
  
  
  One thing Base64 is not: encryption
&lt;/h2&gt;

&lt;p&gt;Anyone can decode a Base64 string instantly. It hides nothing. Never use it to "protect" a password or secret — use real encryption for that. Base64 is for transport and compatibility, not security.&lt;/p&gt;

&lt;p&gt;BulkTextKit Pro is a $19 one-time payment that adds bulk processing for up to 50 texts at once, saved presets, and CSV export — the 15 free tools stay free.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tools</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Compare Two Versions of a Text and See Exactly What Changed</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Tue, 27 Oct 2026 05:21:09 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/how-to-compare-two-versions-of-a-text-and-see-exactly-what-changed-31nf</link>
      <guid>https://dev.to/sujalkhatri/how-to-compare-two-versions-of-a-text-and-see-exactly-what-changed-31nf</guid>
      <description>&lt;p&gt;You edited a draft. A client sent back a revised brief. A config file changed before a deploy. Now you have two versions of the same text — and eyeballing them line by line is how small, important changes slip through.&lt;/p&gt;

&lt;p&gt;A text diff solves this in seconds. Paste the old version on one side and the new version on the other, and it shows you exactly what changed: lines added, lines removed, and lines that stayed the same, with a count for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this actually helps
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A revised draft.&lt;/strong&gt; An editor returns your article with edits. Instead of re-reading the whole thing, diff the two versions and jump straight to what moved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A config or list before and after a change.&lt;/strong&gt; Two versions of a settings file, a price list, or a mailing list — the diff tells you which entries were added or dropped, so nothing disappears quietly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Client feedback rounds.&lt;/strong&gt; Keep version 1 and version 2. When round 3 comes back, you can prove what changed between each round instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to do it (free, no upload)
&lt;/h2&gt;

&lt;p&gt;This is a line-by-line comparison, which is the right level for drafts, lists, and configs. It is not a word-by-word merge tool, and it does not try to be — line-level changes are what you actually need to review.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://bulktextkit.gt.tc/tools/text-diff.php?utm_source=devto" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc/tools/text-diff.php?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste the original on the left, the modified version on the right, and click Compare. Additions show in green, removals in red, and you get counts: how many lines added, removed, and unchanged.&lt;/p&gt;

&lt;p&gt;One thing that matters for work documents: the comparison runs entirely in your browser. Your drafts never get uploaded to a server, which is the whole point when the text is a client's unreleased copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A habit worth keeping
&lt;/h2&gt;

&lt;p&gt;Before you send any revised document onward, diff it against the previous version once. It takes ten seconds, and it catches the deleted paragraph, the changed number, and the dropped line that a skim-read misses every time.&lt;/p&gt;

&lt;p&gt;BulkTextKit Pro ($19, one-time) adds bulk diffing for up to 50 text pairs at once — the free tool itself stays free and complete.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>tools</category>
      <category>webdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>How to Turn Any Blog Title Into a Clean URL Slug (and Why It Matters for SEO)</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:25:30 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/how-to-turn-any-blog-title-into-a-clean-url-slug-and-why-it-matters-for-seo-4nej</link>
      <guid>https://dev.to/sujalkhatri/how-to-turn-any-blog-title-into-a-clean-url-slug-and-why-it-matters-for-seo-4nej</guid>
      <description>&lt;p&gt;A URL slug is the readable tail of a link — the part after the domain. For example, in example.com/blog/10-ways-to-fix-your-garden-spring-2026, the slug is "10-ways-to-fix-your-garden-spring-2026". A clean slug helps readers and search engines understand the page before they click it.&lt;/p&gt;

&lt;p&gt;The rules are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All lowercase — mixed case in URLs causes duplicate-content confusion.&lt;/li&gt;
&lt;li&gt;Words joined with hyphens, not underscores or spaces.&lt;/li&gt;
&lt;li&gt;No special characters — question marks, exclamation marks, ampersands and em dashes have to go.&lt;/li&gt;
&lt;li&gt;Accents transliterated — "Café Menu 2026" becomes "cafe-menu-2026", not a broken string of percent-codes.&lt;/li&gt;
&lt;li&gt;Short beats long — keeping a slug under about 60 characters is a good rule of thumb. Cut filler words rather than the meaning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
"10 Ways to Fix Your Garden — Spring 2026!" becomes "10-ways-to-fix-your-garden-spring-2026".&lt;br&gt;
"Café Menu 2026" becomes "cafe-menu-2026".&lt;/p&gt;

&lt;p&gt;Doing this by hand for one post is fine. Doing it for fifty posts is where a tool earns its keep. I built a free one that does exactly this in your browser — paste a title, the slug appears as you type, nothing uploads anywhere: &lt;a href="https://bulktextkit.gt.tc/tools/slug-generator.php?utm_source=devto" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc/tools/slug-generator.php?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One last line, not a pitch: the same site's Pro plan ($19 one-time) adds a bulk mode that turns 50 titles into 50 slugs at once; the free slug tool itself stays free.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
    <item>
      <title>A user asked for 'remove ALL copies' — so I built it</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Sat, 24 Oct 2026 17:19:42 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/a-user-asked-for-remove-all-copies-so-i-built-it-17ge</link>
      <guid>https://dev.to/sujalkhatri/a-user-asked-for-remove-all-copies-so-i-built-it-17ge</guid>
      <description>&lt;p&gt;I run BulkTextKit — 15 free text tools that run 100% in the browser (no uploads, no sign-up). Last week someone wrote in with a list problem I hadn't thought about properly.&lt;/p&gt;

&lt;p&gt;The usual 'remove duplicate lines' keeps ONE copy of each repeated line. But this person needed the opposite: if a line appears more than once, delete EVERY copy — because in their list, a repeated entry meant bad data on both entries, not one good + one spare.&lt;/p&gt;

&lt;p&gt;So 'Remove ALL copies of repeated lines' is now a tool of its own. Paste the list, click once, and only the true one-offs survive. Like everything on the site it runs locally in your browser — your text never leaves the page, which matters when the list is customer emails or order IDs.&lt;/p&gt;

&lt;p&gt;It sits next to the classic remove-duplicates, plus case fixing, sorting, find-and-replace, word counting and a handful of other paste-clean-paste tools. Pro is a one-time $19 for the extra utilities, but the cleaners are free.&lt;/p&gt;

&lt;p&gt;If you clean lists for a living — support exports, lead lists, inventory dumps — I'd genuinely like to hear which clean-up job you wish existed. That last feature exists because one person asked.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I built 15 free text tools that run 100% in your browser — no uploads, no sign-up</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Sat, 24 Oct 2026 12:18:30 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/i-built-15-free-text-tools-that-run-100-in-your-browser-no-uploads-no-sign-up-2bh8</link>
      <guid>https://dev.to/sujalkhatri/i-built-15-free-text-tools-that-run-100-in-your-browser-no-uploads-no-sign-up-2bh8</guid>
      <description>&lt;p&gt;You know that moment when a list that should take two minutes turns into twenty?&lt;/p&gt;

&lt;p&gt;You paste an export into Excel and find duplicate lines everywhere. You have a block of text with email addresses buried in it, and you need just the addresses. Or an unsorted list needs sorting, cleaning, and copying back — and every tool you open either wants an upload, an account, or a subscription first.&lt;/p&gt;

&lt;p&gt;I kept running into that, especially in Notepad++ and spreadsheets, where simple clean-up jobs still meant fiddly steps, extra columns, or copy-pasting between apps. So I built something small and practical for exactly those jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;BulkTextKit is a set of 15 free text tools that run entirely in your browser. There is nothing to install, no account to create, and no file to upload.&lt;/p&gt;

&lt;p&gt;The tools cover the jobs I actually do most often, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove duplicate lines&lt;/li&gt;
&lt;li&gt;Remove ALL copies of a repeated line — the one I could not find anywhere else, because most tools keep one copy and I sometimes need the line gone completely&lt;/li&gt;
&lt;li&gt;Sort and shuffle lines&lt;/li&gt;
&lt;li&gt;Extract emails and URLs from a block of text&lt;/li&gt;
&lt;li&gt;Word and character count&lt;/li&gt;
&lt;li&gt;Find and replace in bulk&lt;/li&gt;
&lt;li&gt;And more everyday line and text clean-up tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can try it here: &lt;a href="https://bulktextkit.gt.tc" rel="noopener noreferrer"&gt;BulkTextKit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it runs in your browser
&lt;/h2&gt;

&lt;p&gt;The part I care about most is privacy, in a very practical sense: your text never leaves your device. Paste it in, clean it, copy the result out. That makes it a much easier choice for lists, notes, and exports you would rather not send to a server.&lt;/p&gt;

&lt;p&gt;It also means there is no sign-up step slowing you down, and once the page is loaded it can keep working offline.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I use it
&lt;/h2&gt;

&lt;p&gt;My usual flow is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Paste the messy text.&lt;/li&gt;
&lt;li&gt;Pick the tool — for example, remove duplicates, sort the lines, or extract the emails.&lt;/li&gt;
&lt;li&gt;Check the result, then copy it back into a spreadsheet, document, or editor.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It is not trying to replace Excel or a full editor. It is for that middle step: getting text into decent shape before you do the real work with it.&lt;/p&gt;

&lt;p&gt;If you clean up lists, exports, or pasted text regularly, I hope it saves you a few of those twenty-minute detours.&lt;/p&gt;

&lt;p&gt;You can find the 15 free tools here: &lt;a href="https://bulktextkit.gt.tc" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a one-time $19 Pro for power features; the 15 core tools are free forever. Feedback welcome — I would love to hear which tool you use most, or what is missing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to delete EVERY copy of a duplicate line (not just the extras)</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Fri, 23 Oct 2026 14:24:31 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/how-to-delete-every-copy-of-a-duplicate-line-not-just-the-extras-28jg</link>
      <guid>https://dev.to/sujalkhatri/how-to-delete-every-copy-of-a-duplicate-line-not-just-the-extras-28jg</guid>
      <description>&lt;p&gt;Most dedupe tools keep one copy of each repeated line and delete the rest. But sometimes that's the wrong result. Sometimes a value that appears twice is bad data, and you want it gone completely — only the lines that appear exactly once should survive.&lt;/p&gt;

&lt;p&gt;Real examples: sign-ups who registered twice and must be excluded entirely, customers who appear in two monthly exports when you only want the new ones, or SKUs that show up twice in a supplier file with different prices (keep neither until a human checks).&lt;/p&gt;

&lt;p&gt;Here are the ways to do it, from spreadsheets to one paste.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Excel / Google Sheets (modern versions)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=UNIQUE(A2:A1000, FALSE, TRUE)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third argument is the trick: TRUE means "only rows that occur exactly once". Every copy of a repeated value is excluded.&lt;/p&gt;

&lt;h2&gt;
  
  
  With a helper column (any version)
&lt;/h2&gt;

&lt;p&gt;Put this next to your data and fill down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=COUNTIF($A:$A, A2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filter for 1 and you have the appear-once list. Filter for anything above 1 and you see every copy of every repeat, grouped for deletion.&lt;/p&gt;

&lt;h2&gt;
  
  
  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="nb"&gt;sort &lt;/span&gt;list.txt | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;uniq -u&lt;/code&gt; prints only lines that are not repeated. Its partner &lt;code&gt;uniq -d&lt;/code&gt; prints one copy of each repeated line — together they split any list into "appeared once" and "appeared more than once".&lt;/p&gt;

&lt;h2&gt;
  
  
  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;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;only_once&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps the original order, which the sort-based methods don't.&lt;/p&gt;

&lt;h2&gt;
  
  
  One paste, in the browser
&lt;/h2&gt;

&lt;p&gt;If the list is a single column and you don't want formulas, I built a free tool for exactly this: &lt;a href="https://bulktextkit.gt.tc/tools/remove-duplicate-lines.php" rel="noopener noreferrer"&gt;Remove Duplicate Lines&lt;/a&gt; — paste the list, tick &lt;strong&gt;"Remove ALL copies of repeated lines"&lt;/strong&gt;, and only the lines that appear once stay. It runs entirely in your browser; nothing is uploaded. (Full disclosure: the tool is mine — it's free, and the default mode on the same page is the normal keep-one dedupe.)&lt;/p&gt;

&lt;p&gt;If you clean lists like this every week, the same toolset has a Pro version ($19 one-time) with a bulk mode — up to 50 lists in a single run, saved presets, CSV export. The free page above covers the single-list job completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  One warning before you dedupe anything
&lt;/h2&gt;

&lt;p&gt;Trailing spaces and casing make identical-looking lines count as different values. "apple " and "apple" are different strings. Trim first (and ignore case if your data needs it), or your "duplicates" will quietly survive.&lt;/p&gt;

</description>
      <category>excel</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>csv</category>
    </item>
    <item>
      <title>Test Your Regex in the Browser: 5 Real Bugs + a Free Online Tester</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Fri, 23 Oct 2026 05:23:38 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/test-your-regex-in-the-browser-5-real-bugs-a-free-online-tester-131m</link>
      <guid>https://dev.to/sujalkhatri/test-your-regex-in-the-browser-5-real-bugs-a-free-online-tester-131m</guid>
      <description>&lt;p&gt;Every week I lose 20 minutes to the same dumb regex bug. Not a clever one — a &lt;em&gt;dumb&lt;/em&gt; one. The pattern looks right, the test string looks right, and the match count says 0.&lt;/p&gt;

&lt;p&gt;Regex has a tiny set of failure modes and they repeat forever. Here are the five I actually hit, with the fix for each. Paste them into any tester as you read — seeing the match change is the whole lesson.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Greedy &lt;code&gt;.*&lt;/code&gt; eats your whole string
&lt;/h3&gt;

&lt;p&gt;You want the quoted words out of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;say "hi" then "bye"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pattern &lt;code&gt;".*"&lt;/code&gt; matches &lt;code&gt;"hi" then "bye"&lt;/code&gt; — one giant match, because &lt;code&gt;.*&lt;/code&gt; is greedy and grabs as much as it can.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; make it lazy with &lt;code&gt;?&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;".*?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you get &lt;code&gt;"hi"&lt;/code&gt; and &lt;code&gt;"bye"&lt;/code&gt; separately. If you want the inside without quotes, capture it: &lt;code&gt;"([^"]*)"&lt;/code&gt; — group 1 holds the words.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; don't mean "start of line" by default
&lt;/h3&gt;

&lt;p&gt;Multiline log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error 404 page missing
error 500 db down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pattern &lt;code&gt;^\w+ \d+&lt;/code&gt; matches only the &lt;em&gt;first&lt;/em&gt; line. Without the &lt;code&gt;m&lt;/code&gt; (multiline) flag, &lt;code&gt;^&lt;/code&gt; means "start of the whole string", not "start of each line".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; turn on the &lt;code&gt;m&lt;/code&gt; flag. Then both lines match. This is the #1 reason a pattern "works on one line but not on the file."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The dot doesn't match newlines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start
middle
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pattern &lt;code&gt;start.*end&lt;/code&gt; matches &lt;strong&gt;nothing&lt;/strong&gt; — &lt;code&gt;.&lt;/code&gt; matches any character &lt;em&gt;except&lt;/em&gt; a newline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use &lt;code&gt;[\s\S]*&lt;/code&gt; instead of &lt;code&gt;.*&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;start[\s\S]*end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;\s&lt;/code&gt; = whitespace including newlines, &lt;code&gt;\S&lt;/code&gt; = everything else, together = literally everything.) In JavaScript you can also use the &lt;code&gt;s&lt;/code&gt; (dotAll) flag — &lt;code&gt;start.*end&lt;/code&gt; with &lt;code&gt;s&lt;/code&gt; on — but &lt;code&gt;[\s\S]&lt;/code&gt; works in every flavor.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. You forgot to escape the special characters
&lt;/h3&gt;

&lt;p&gt;Matching an IP address:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\d+.\d+.\d+.\d+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This "works" on &lt;code&gt;192.168.1.1&lt;/code&gt; — and also happily matches &lt;code&gt;192x168y1z1&lt;/code&gt;, because an unescaped &lt;code&gt;.&lt;/code&gt; means &lt;em&gt;any character&lt;/em&gt;. It passes your test, then breaks in production on real data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; escape the dots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\d+\.\d+\.\d+\.\d+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule of thumb: if you typed &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;(&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; &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;\&lt;/code&gt; and you meant the &lt;em&gt;literal character&lt;/em&gt;, put a backslash in front of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Capturing groups you never needed
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(\d{4})-(\d{2})-(\d{2})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;matches &lt;code&gt;2026-10-23&lt;/code&gt; and gives you 3 groups. Fine. But this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(foo|bar|baz)+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;captures a group on every repetition you never look at, which clutters the match table and (on huge inputs) costs time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; non-capturing groups with &lt;code&gt;(?:...)&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;(?:foo|bar|baz)+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same match, no junk groups. Use &lt;code&gt;(...)&lt;/code&gt; when you need the captured value; &lt;code&gt;(?:...)&lt;/code&gt; everywhere else.&lt;/p&gt;




&lt;h3&gt;
  
  
  A debugging workflow that actually works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Paste real sample text&lt;/strong&gt;, not a toy one-liner — bugs hide in the second line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toggle one flag at a time&lt;/strong&gt; (&lt;code&gt;i&lt;/code&gt;, &lt;code&gt;m&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;) and watch the match count change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the groups table&lt;/strong&gt;, not just the highlight — half of regex bugs are "it matched, but captured the wrong part."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The free tester I built
&lt;/h3&gt;

&lt;p&gt;I make &lt;a href="https://bulktextkit.gt.tc/?utm_source=devto" rel="noopener noreferrer"&gt;BulkTextKit&lt;/a&gt; — 15 free text tools that run 100% in your browser, nothing uploaded. The &lt;a href="https://bulktextkit.gt.tc/tools/regex-tester.php?utm_source=devto" rel="noopener noreferrer"&gt;Regex Tester&lt;/a&gt; is the one I open myself: live match highlighting with positions, captured groups in a table, flag toggles for ignore-case / multiline / global, JavaScript's RegExp engine (the same syntax your Node and browser code uses), up to 500 matches shown.&lt;/p&gt;

&lt;p&gt;One line on Pro: &lt;a href="https://bulktextkit.gt.tc/pro.php?utm_source=devto" rel="noopener noreferrer"&gt;BulkTextKit Pro&lt;/a&gt; ($19 one-time, no subscription) adds Bulk mode — test one pattern against up to 50 texts at once instead of pasting them one by one.&lt;/p&gt;

&lt;p&gt;What's the dumbest regex bug that's ever cost you an hour? I want to feel less alone in bug #2.&lt;/p&gt;




</description>
      <category>regex</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Convert Case Online: UPPERCASE, lowercase, Title Case &amp; Sentence Case — Free, No Sign-Up</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Tue, 20 Oct 2026 05:47:15 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/convert-case-online-uppercase-lowercase-title-case-sentence-case-free-no-sign-up-3ief</link>
      <guid>https://dev.to/sujalkhatri/convert-case-online-uppercase-lowercase-title-case-sentence-case-free-no-sign-up-3ief</guid>
      <description>&lt;p&gt;You know the situation. A client sends you a document typed entirely in CAPS LOCK. Or you paste a headline into your CMS and it's in the wrong style. Or an export gives you 200 product names in lowercase and the store needs Title Case.&lt;/p&gt;

&lt;p&gt;Retyping is the slow way. Here's what each case style actually means, when to use it, and the fastest ways to convert.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6 case styles, explained
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UPPERCASE&lt;/strong&gt; — EVERY LETTER CAPITALIZED. For short labels, warnings, and acronyms. Never for paragraphs — all-caps text is measurably harder to read and looks like shouting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;lowercase&lt;/strong&gt; — every letter small. Used for code identifiers, URLs, email addresses, and some brand styles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Title Case&lt;/strong&gt; — The First Letter Of Every Word Is Capitalized. The standard for headlines, article titles, book titles, and product names in most US style guides. Small words like "a", "the", and "of" are usually kept lowercase in stricter styles — most converters capitalize every word, so give long titles a quick glance before publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sentence case&lt;/strong&gt; — Only the first letter of each sentence is capitalized. This is what UK publications and most modern documentation use for headings, and it's the easiest to read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aLtErNaTiNg cAsE&lt;/strong&gt; — alternating capitals. Mostly for memes and emphasis jokes. You will rarely need it for work, but when you do, you definitely don't want to type it by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InVeRsE cAsE&lt;/strong&gt; — the exact opposite of whatever you typed. Niche, but handy for fixing text where Caps Lock was on by accident mid-document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to convert without a dedicated tool
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Word / Google Docs:&lt;/strong&gt; select the text and press &lt;code&gt;Shift + F3&lt;/code&gt; (Word) to cycle through lowercase → UPPERCASE → Capitalize Each Word. Works, but cycles one document at a time and mangles mixed content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSS (display only):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.title&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;capitalize&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;Warning: &lt;code&gt;text-transform&lt;/code&gt; only changes how text &lt;em&gt;looks&lt;/em&gt;. Copy-paste the text somewhere else and the original case comes back. Fine for styling, wrong for fixing source text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript (one string at a time):&lt;/strong&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;titleCase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&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="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;\b\w&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great if you're already in a console. Useless if you're converting a client's 5,000-word document or fifty file names.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fastest way for real documents
&lt;/h2&gt;

&lt;p&gt;Paste the text into a &lt;a href="https://bulktextkit.gt.tc/tools/case-converter.php?utm_source=devto" rel="noopener noreferrer"&gt;case converter&lt;/a&gt;, click the style, copy the result. That's the whole workflow — no sign-up, and the conversion runs in your browser, so the text never leaves your device (worth caring about when the text is a client's unreleased draft).&lt;/p&gt;

&lt;p&gt;One honest tip: whatever tool you use, read the converted text once before shipping it. Converters handle 99% of text mechanically — but brand names (iPhone, eBay), acronyms (API, NASA), and names with internal capitals are exactly the cases no mechanical rule gets right every time.&lt;/p&gt;




&lt;p&gt;I built the converter above as part of BulkTextKit — 15 free browser text tools. If you regularly convert text in bulk (up to 50 texts at once), Pro adds batch processing, saved presets and CSV export for a one-time $19: &lt;a href="https://bulktextkit.gt.tc/pro.php?utm_source=devto" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc/pro.php?utm_source=devto&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Word Counter Online: Count Words, Characters and Reading Time — No Sign-Up, Nothing Uploaded</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:39:50 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/word-counter-online-count-words-characters-and-reading-time-no-sign-up-nothing-uploaded-3k2d</link>
      <guid>https://dev.to/sujalkhatri/word-counter-online-count-words-characters-and-reading-time-no-sign-up-nothing-uploaded-3k2d</guid>
      <description>&lt;p&gt;Counting words sounds trivial until the tool wants your email first.&lt;/p&gt;

&lt;p&gt;I kept running into the same wall: paste a draft into a random word counter, get hit with a sign-up wall, or wonder where my text just got uploaded. So I built my own — it runs entirely in your browser. Your text never leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it counts, live as you type:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Words and characters (with and without spaces)&lt;/li&gt;
&lt;li&gt;Sentences and paragraphs&lt;/li&gt;
&lt;li&gt;Reading time and speaking time&lt;/li&gt;
&lt;li&gt;Longest word, and average word length&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's free, no account, no uploads: &lt;a href="https://bulktextkit.gt.tc/tools/word-counter.php?utm_source=devto" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc/tools/word-counter.php?utm_source=devto&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is one of 15 free text tools on BulkTextKit — case converter, duplicate line remover, JSON formatter, regex tester, diff checker and more, all plain PHP pages with the work happening client-side.&lt;/p&gt;

&lt;p&gt;The Pro version ($19 one-time) adds bulk batch processing — drop in many texts at once — plus presets and CSV export.&lt;/p&gt;

&lt;p&gt;If you write, edit, or ship content for a living, I'd genuinely like to know: which count do you actually check first — words, characters, or reading time?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
      <category>php</category>
    </item>
    <item>
      <title>JSON Validator &amp; Formatter: How to Fix Invalid JSON Fast (Without Installing Anything)</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Fri, 16 Oct 2026 21:04:28 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/json-validator-formatter-how-to-fix-invalid-json-fast-without-installing-anything-11f1</link>
      <guid>https://dev.to/sujalkhatri/json-validator-formatter-how-to-fix-invalid-json-fast-without-installing-anything-11f1</guid>
      <description>&lt;p&gt;JSON is everywhere — APIs, config files, package.json — and it breaks in the most annoying ways. Here are the 5 errors I see most often, and the fastest ways to find and fix them without installing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Trailing commas
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;That comma after &lt;code&gt;"Jaipur"&lt;/code&gt; kills the whole file. JSON does not allow trailing commas, period. Delete it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Single quotes instead of double quotes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'name':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Sujal'&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;Looks fine, is not JSON. Keys AND string values must use double quotes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sujal"&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;h2&gt;
  
  
  3. Unquoted keys
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sujal"&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;Valid JavaScript object, invalid JSON. Quote every key.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Comments
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;details&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;"Sujal"&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;JSON has no comments. If you need them, use JSONC in your editor — but strip them before sending to an API.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. NaN, Infinity, undefined
&lt;/h2&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;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;NaN&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;These are JavaScript things, not JSON things. Use &lt;code&gt;null&lt;/code&gt; or a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fastest ways to validate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Browser console (zero setup):&lt;/strong&gt; paste your JSON into &lt;code&gt;JSON.parse('...')&lt;/code&gt;. The error message tells you the exact character position where it broke. That's usually enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python (already on most machines):&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"a":1,}'&lt;/span&gt; | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It pretty-prints valid JSON and points at the error for invalid JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;jq (if you live in the terminal):&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"a":1}'&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Formatting: pick one style and stick to it
&lt;/h2&gt;

&lt;p&gt;2-space indent is the most common. Tabs are fine too. What matters is consistency — mixed indentation makes diffs unreadable and code reviews painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real before/after
&lt;/h2&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;'users':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Amit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;age:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Priya'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;age:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;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;After:&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;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;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;"Amit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="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;"Priya"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four fixes: quotes, keys, trailing commas, and it's done.&lt;/p&gt;




&lt;p&gt;I got tired of switching between five different sites for this kind of work, so I built BulkTextKit — 15 free text tools in the browser, including a JSON formatter/validator that points at the exact error. Pro (batch processing, presets, CSV export) is $19: &lt;a href="https://bulktextkit.gt.tc" rel="noopener noreferrer"&gt;https://bulktextkit.gt.tc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>json</category>
      <category>webdev</category>
      <category>tools</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I built 15 free text tools with plain PHP and zero APIs — here's what I learned</title>
      <dc:creator>Sujal Khatri</dc:creator>
      <pubDate>Wed, 14 Oct 2026 17:34:12 +0000</pubDate>
      <link>https://dev.to/sujalkhatri/i-built-15-free-text-tools-with-plain-php-and-zero-apis-heres-what-i-learned-18kh</link>
      <guid>https://dev.to/sujalkhatri/i-built-15-free-text-tools-with-plain-php-and-zero-apis-heres-what-i-learned-18kh</guid>
      <description>&lt;p&gt;This week I launched a new product: BulkTextKit, a set of 15 free text tools — word counter, case converter, JSON formatter/validator, regex tester, slug generator, and more — that run entirely in your browser. No signup, no uploads, no tracking. I'm a solo maker in India, I build with plain PHP + MySQL, and I made one unusual choice on purpose: zero third-party APIs. Everything the tools do happens in client-side JavaScript; the server only renders pages and handles the Pro license flow through PayPal. In this post I'll walk through why I chose that architecture, how the build went, and the one-time pricing model I'm betting on instead of yet another subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem I kept hitting
&lt;/h2&gt;

&lt;p&gt;Every day I run into the same tiny text chores: counting words in client copy, converting a heading to title case, stripping duplicate lines from a messy list, or checking whether a snippet of JSON is actually valid. These jobs take a minute each, but they pile up when you work with words all day.&lt;/p&gt;

&lt;p&gt;The free tools I found online were scattered across dozens of ad-heavy pages, and most of them sent my text to some server I knew nothing about. I stopped trusting them for anything sensitive, which meant I kept doing these chores by hand or wiring up throwaway scripts. That friction is the problem BulkTextKit exists to erase: one fast page, fifteen tools, nothing leaves your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PHP + zero APIs
&lt;/h2&gt;

&lt;p&gt;One deliberate decision shaped the whole product: zero third-party APIs. No API keys means nothing to leak, nothing to rotate, and no third-party service that can be shut down, rate-limited, or bought and sunsetted underneath me.&lt;/p&gt;

&lt;p&gt;It also means the site runs on cheap shared hosting with plain PHP and MySQL. There is no build pipeline, no framework to keep updated, and no SDK that can break a deploy. For a solo maker in India, boring infrastructure is a feature, not a compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the tools work
&lt;/h2&gt;

&lt;p&gt;All fifteen tools run 100% in browser JavaScript — Word Counter, Case Converter, Remove Duplicate Lines, JSON Formatter &amp;amp; Validator, Regex Tester, Slug Generator, Lorem Ipsum Generator, Text Diff, CSV to Columns, Remove Line Breaks, Word Frequency Counter, Base64 Encoder/Decoder, Markdown Table Generator, Strip HTML, and Password Generator.&lt;/p&gt;

&lt;p&gt;PHP only renders the pages and handles the Pro license flow, including the PayPal IPN handshake that activates a purchase. Your text never touches my server, which is exactly the point: you can paste client work into these tools without wondering where it went.&lt;/p&gt;

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

&lt;p&gt;All fifteen tools are free forever — that is the product, not a trial. Pro is a one-time payment of $19, unlocking bulk/batch mode for up to 50 texts at once, saved presets for repeat tasks, and CSV export. Checkout is via PayPal, and every purchase is covered by a 7-day money-back guarantee.&lt;/p&gt;

&lt;p&gt;I am betting on one-time pricing instead of yet another subscription because these are small utilities, not a platform. The launch offer is live now at &lt;a href="https://bulktextkit.gt.tc" rel="noopener noreferrer"&gt;BulkTextKit&lt;/a&gt; — try the free tools and tell me: which tool should I add next?&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I built this again, I would start marketing much earlier. I waited until launch week to talk about it publicly, which meant I launched to no audience at all. I would also ship smaller: instead of polishing fifteen tools before telling anyone, I would have launched with five and let feedback pick the rest.&lt;/p&gt;

&lt;p&gt;Both lessons point the same way: build in the open from day one. I am starting now, so expect to see more of the process, not just the finished product.&lt;/p&gt;

</description>
      <category>php</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>tools</category>
    </item>
  </channel>
</rss>
