String Escape & Unescape
Escape a string for the context you are about to put it in, or strip escaping from something you received.
There is no such thing as "escaped"
The same string needs five different treatments depending on where it goes. A double
quote is harmless inside JSON element content but must be written
" in an HTML attribute and doubled as "" in a CSV
field. An ampersand means nothing to JavaScript and starts an entity reference in HTML.
This is why "escape everything on the way in" is the wrong model and causes so many
double-escaping bugs. Store text raw, and escape it
at the moment it crosses into a new context. Text that lives in your
database should be the actual text; O'Brien stored in a database is a
bug waiting to be double-escaped into O'Brien when it is displayed.
| Context | Characters that matter | Consequence of getting it wrong |
|---|---|---|
| JSON string | " \ and control characters | Invalid JSON, parser rejects the whole document |
| JavaScript string | ' \ newlines, U+2028 | Broken script or injected code |
| HTML text | & < > | Cross-site scripting |
| HTML attribute | the above plus your quote character | Attribute injection, breaking out of the value |
| XML text | & <, plus ]]> | Not well-formed; parse fails entirely |
| CSV field | " , newline | Silently shifted columns on import |
| SQL literal | ', and depending on the engine \ | SQL injection |
| URL component | & = ? % space | Truncated or misparsed query strings |
Escaping is not a defence, it is a correctness measure. For SQL, the correct answer is a parameterised query, not escaping — escaping can be defeated by charset tricks and multi-byte edge cases in ways that binding a parameter cannot. For HTML, prefer a templating engine that escapes by default over remembering to do it yourself. Use escaping for the cases where it genuinely is the mechanism, such as CSV and URLs.
Why the order of replacements matters
Escape & first, always. If you replace < with
< first and then escape ampersands, the ampersand you just inserted
becomes & and your output reads literally as
< on screen. The same trap applies in reverse when unescaping: decode
in one pass over the original text, never repeatedly until the output stops changing,
because that lets &lt; collapse all the way to
< — turning a doubly-escaped harmless string into live markup.
UTF-8 changed the problem, it did not remove it
Encoding issues used to be the main source of escaping bugs. With everything on UTF-8,
the remaining traps are narrower but real: characters that look like ASCII but are not
(the Unicode minus sign, smart quotes, non-breaking space), line separators such as
U+2028 that are legal in JSON and illegal in JavaScript string literals
before ES2019, and combining characters that change how a string compares while looking
identical.
Frequently asked questions
Should I store text escaped or raw?
Raw, always. Escape when you move the text into a context that needs it. Storing pre-escaped text means every consumer has to know which context it was escaped for, and that is the origin of double-escaped output.
Why does my CSV break when opened in Excel?
Usually a field containing a comma, quote or newline that was not quoted. Excel also guesses types, so values like 1-2 or leading-zero identifiers get mangled regardless of escaping — import with explicit column types rather than double-clicking the file.
Is encodeURIComponent the same as URL escaping?
It is the right function for a single component — a query value or path segment. It is wrong for a whole URL, because it escapes the colon, slashes and question mark that give the URL its structure. Escaping a complete URL needs a different approach entirely.
What about the plus sign in a URL?
Form encoding uses + for a space; percent-encoding uses %20. They are not interchangeable. In a query string, a literal plus must be %2B, or it will be decoded as a space by a form-style decoder.
Privacy. Conversion runs in your browser. Nothing is uploaded, so it is safe to work with real customer data or credentials.