What the error actually means
SyntaxError: Bad control character in string literal in JSON (Chrome/V8) — or "Invalid control character" / "Unexpected control character" in other engines — means a raw control character sits inside a JSON string where it isn't allowed. The JSON spec forbids unescaped control characters (code points U+0000 through U+001F) inside string values. The usual culprits are a literal newline, a tab, or a carriage return that ended up *inside the quotes* instead of being written as an escape sequence.
The parser is very specific here: the JSON *structure* is fine, but one string value contains a byte it isn't allowed to contain literally. This is different from Unexpected token at position 0 (which usually means you got HTML) and Unexpected end of JSON input (truncated data) — this one is about escaping.
The classic reproduction
A literal line break inside a string value triggers it immediately:
// ❌ raw newline inside the string → "Bad control character"
JSON.parse('{ "note": "line one
line two" }');JSON requires that newline to be the two-character escape \n, not an actual line break:
// ✅ escaped newline → parses fine
JSON.parse('{ "note": "line one\nline two" }'); // { note: "line one
line two" }Inside a JSON string, these characters must be escaped:
| Character | Must be written as |
|---|---|
| Newline | \n |
| Carriage return | \r |
| Tab | \t |
| Backspace | \b |
| Form feed | \f |
| Any other control char (U+0000–U+001F) | \uXXXX |
Where it comes from in real code
You rarely type a raw newline on purpose — it sneaks in:
- Pasting multi-line text (a log excerpt, a stack trace, a code snippet, an address) directly into a
.jsonfile or a string literal. The line breaks you see are real newline bytes. - Building JSON by hand with string concatenation — e.g.
'{"msg":"' + userText + '"}'. IfuserTextcontains a newline or tab, you've produced invalid JSON. This is the number-one cause and the reason you should never assemble JSON with string joining. - Copying data from a spreadsheet or terminal, which carries tabs and carriage returns you can't see.
- Windows line endings (
\r\n) in text that gets embedded into a string value.
The right fix: let a serializer do the escaping
If you're generating the JSON, never build it by hand — use JSON.stringify, which escapes control characters correctly and automatically:
// ✅ stringify handles the escaping for you
const note = "line one
line two with a tab";
const json = JSON.stringify({ note });
// '{"note":"line one\nline two\twith a tab"}' → always valid
JSON.parse(json); // round-trips cleanlyThe moment you find yourself concatenating strings to make JSON, stop and use JSON.stringify (or your language's equivalent — Python's json.dumps, Go's json.Marshal). They exist precisely to get escaping right.
Fixing JSON you received
If the broken JSON came from *someone else* and you can't change the producer:
- Find the offending value. The error often reports a position; format the raw text and look for a string that visibly wraps onto a new line inside its quotes.
- Escape the control characters — replace real newlines with
\n, tabs with\t. The JSON escape tool does this for a pasted value. - Re-validate with a JSON validator to confirm the string is now clean, and try the JSON fixer for a quick automated repair of common escaping issues.
Prevention checklist
- Generate JSON with a serializer, never with string concatenation or template literals.
- Escape user-supplied text before it enters a JSON string if you must handle it manually.
- Normalize line endings on ingest if you embed multi-line text.
- Validate at the boundary so malformed JSON is caught on entry, not three functions later.
For the broader set of parse failures see the JSON parse errors guide and common JSON mistakes.