DevTools Hub

Search tools

Search for a developer tool

String Escaping Explained

Part of the Encoding Toolkit

"Escaping," "encoding," and "sanitizing" get used interchangeably in casual conversation, and that's exactly where real bugs come from — they're three different operations, each solving a different problem, and doing the wrong one leaves you with data that looks safe and isn't. This is the distinction, and the mental model that makes escaping specifically make sense across every context it shows up in — JSON, JavaScript, SQL, HTML, and regex.

What escaping actually is

Escaping marks a character so it's treated literally instead of according to whatever special meaning it would otherwise have in the syntax it's embedded in. A double quote inside a JSON string would normally end the string — \" tells the JSON parser "no, this is a literal quote character, keep reading the string." That's the entire idea: the underlying data doesn't change, only how it's represented so the surrounding syntax doesn't misinterpret it. Escaping is reversible by design — unescape(escape(x)) === x for any valid input, or the escaping scheme is broken.

Escaping vs. encoding vs. sanitizing

  • Escaping neutralizes specific characters' special meaning within one syntax — a string literal, a regex pattern, an HTML document. The data itself is unchanged; only certain characters get a different textual representation.
  • Encoding represents the same data in a completely different format — Base64 turning arbitrary bytes into ASCII text, percent-encoding turning a character into %XX. Encoding isn't about neutralizing syntax; the encoded form usually isn't valid in the original context at all until decoded back.
  • Sanitizing is judgment-based and can genuinely lose information — decide a <script> tag in user-submitted HTML shouldn't exist at all and strip it, rather than represent it safely. Unlike escaping and encoding, sanitizing isn't reliably reversible, because the goal was never to preserve the original data, it was to enforce a policy about what's allowed.

The distinction matters in practice: "I encoded it, so it's safe" and "I escaped it, so it's safe" are claims about completely different guarantees, and neither one implies the other. Base64-encoding a malicious HTML payload doesn't make it safe to decode and inject into a page later — encoding never claimed to neutralize anything, only to change the format.

Every context has its own alphabet of special characters

This is the part that trips people up most: escaping is always relative to where the text is going, and each destination has its own, unrelated set of characters that need it.

  • JSON strings", \, and control characters. Text Escape handles this per the JSON spec.
  • JavaScript string literals — the JSON set, plus ' and `, since JavaScript has three quoting styles JSON doesn't.
  • SQL string literals — a single quote, doubled (''), the ANSI-standard way to include a literal quote.
  • HTML&, <, >, ", and ', replaced with named or numeric character references. Covered in depth, with the actual security reasoning, in Why HTML Escaping Prevents XSS.
  • Regex patterns — an entirely different set again: ^ $ \ . * + ? ( ) [ ] { } | and /, none of which overlap meaningfully with any of the above. How to Escape Special Characters in Regex covers this one specifically.

None of these substitute for each other. A string that's perfectly safe to embed in a JSON document can still contain .*, which means something very different the moment it's dropped into a regex pattern unescaped. Escaping for the context you're actually writing into — not "escaping" as one generic operation — is the entire game.

The double-escaping trap

Escape data that's already been escaped once and the backslashes (or entity references) themselves get escaped again — \n becomes \\n, &amp; becomes &amp;amp;. This happens constantly in practice when data crosses a boundary more than once without anyone tracking whether it was already escaped on the way in — a value escaped for storage, then escaped again for display, ends up wrong at the display layer even though each individual step was implemented correctly. The fix isn't a cleverer escaping function; it's knowing, at every point data moves, whether it's currently raw or already escaped for a specific context — and escaping exactly once, at the point it's about to enter that context, rather than speculatively upstream.

Try it yourself

Text Escape handles JSON, JavaScript, and SQL string literals; Regex Escape handles regex patterns specifically, including an exact reproduction of JavaScript's native RegExp.escape(); HTML Encode handles markup. All three run entirely in your browser.

Related tools