SyntaxError: Bad control character in string literal in JSON at position N

Quick answer

A raw control character — almost always a literal newline or tab — is sitting inside a JSON string without being escaped. JSON forbids unescaped control characters inside strings, so JSON.parse() stops at it. Escape it (a \n, \t, or \u00XX sequence) or, better, build the JSON with JSON.stringify() instead of by hand.

The exact error string

The wording depends on the engine, but it always names a control character inside a string literal:

// Chrome / V8 / Node.js
SyntaxError: Bad control character in string literal in JSON at position 15

// Firefox
SyntaxError: JSON.parse: bad control character in string literal

// Python (the same problem)
json.decoder.JSONDecodeError: Invalid control character at: line 1 column 16 (char 15)

What “at position N” means

The position is the character index of the offending control character — the exact spot inside a string value where a raw newline, tab, or similar non-printing character appears. JSON only allows those characters in escaped form, so the parser rejects the literal one.

Why JSON forbids this

A JSON string must stay on a single physical line. Characters such as newlines and tabs have to be written as escape sequences (\n, \t, \u00XX) so the parser can reliably tell data apart from document structure — a raw newline could otherwise look like the end of a line of JSON rather than part of a value. Forbidding literal control characters keeps the grammar unambiguous and makes JSON safe to stream line by line.

Cause 1: A literal newline inside a string

The most common case: a real line break between the quotes of a string value. JSON strings cannot span physical lines — the break must be written as a \n escape:

// Backtick template literal with a real line break inside the string value
const bad = `{
  "message": "line one
line two"
}`;
JSON.parse(bad);   // SyntaxError: Bad control character in string literal in JSON

// Correct: the newline is escaped
const good = '{ "message": "line one\\nline two" }';
JSON.parse(good);  // ok

Cause 2: Tabs and other control characters from copy-paste

Pasting text copied from a spreadsheet, terminal, or PDF can drop invisible tab (U+0009) or carriage-return (U+000D) characters straight into a string. They look like ordinary spacing but are control characters:

// The gap below is a literal TAB pasted into the value
const data = '{ "name": "John<TAB>Doe" }';
JSON.parse(data);   // Bad control character — the tab must be escaped

Cause 3: Building JSON by string concatenation

Hand-assembling JSON from variables is the root cause behind most of these errors. If a value contains a newline or tab, you get invalid JSON:

const comment = "Great product!\nWould buy again.";   // contains a real newline

// Wrong — drops the raw newline into the JSON string
const json = '{ "comment": "' + comment + '" }';
JSON.parse(json);   // Bad control character in string literal

// Right — let JSON.stringify do the escaping
const json2 = JSON.stringify({ comment });
JSON.parse(json2);  // ok — the newline is escaped automatically

Cause 4: Control characters from a file or database

Text stored in a database or file can carry embedded control characters that only surface when you wrap the value in JSON and parse it. Treat any externally sourced string as untrusted and re-encode it before parsing.

How to debug it

Use the position from the error to inspect the exact character. A charCodeAt() value between 0 and 31 confirms a control character:

const pos = 15;                            // from "at position 15"
console.log(JSON.stringify(text[pos]));    // shows the escaped character
console.log(text.charCodeAt(pos));         // 9 = tab, 10 = newline, etc.

// Find every control character in the string
[...text].forEach(function (ch, i) {
  if (ch.charCodeAt(0) < 32) console.log(i, ch.charCodeAt(0));
});

The fix: escape control characters or use JSON.stringify

The durable fix is to never build JSON by hand. JSON.stringify() escapes newlines, tabs, and every other control character for you:

// Build JSON from a real object — escaping is automatic
const payload = JSON.stringify({ message: "line one\nline two\twith a tab" });
JSON.parse(payload);   // ok

If you cannot regenerate the JSON and must clean an incoming string, replace every control character (any character below code point 32) with its escape before parsing:

// Escape any character below U+0020 (a control character) so JSON.parse accepts it
const safe = raw.replace(/[\s\S]/g, function (ch) {
    var code = ch.charCodeAt(0);
    if (code < 32) {
        return "\\u" + ("000" + code.toString(16)).slice(-4);
    }
    return ch;
});
const data = JSON.parse(safe);

In Python, the same problem (Invalid control character at) is fixed with json.dumps(), or as a last resort json.loads(text, strict=False) to tolerate literal control characters.

Need to escape a string for JSON safely?

The String Escape tool turns raw text — newlines, tabs, quotes and all — into a valid JSON string literal. Or paste the whole document into the validator to find the bad character. Everything runs in your browser.

String Escape JSON Validator

Frequently Asked Questions

What does Bad control character in string literal in JSON mean?

It means a raw control character — a character with code point U+0000 to U+001F, most often a literal newline (U+000A) or tab (U+0009) — appears inside a JSON string and was never escaped. The JSON specification forbids unescaped control characters inside strings, so JSON.parse() stops at that character. The position number in the message points to the exact offending character.

How do I fix bad control character in JSON?

Escape the control character instead of embedding it raw: use a \n escape for a newline, \t for a tab, \r for a carriage return, or a \u00XX escape for any other control character. The reliable way is to never build JSON by hand — produce it with JSON.stringify(), which escapes control characters for you automatically. If the JSON comes from outside, sanitize or re-encode the offending field before parsing.

Which characters are control characters in JSON?

Control characters are the Unicode code points from U+0000 to U+001F: newline (U+000A), carriage return (U+000D), tab (U+0009), null (U+0000), and the other non-printing characters in that range. JSON allows them inside a string only in escaped form. A literal, unescaped one is a syntax error.

Why does a newline break JSON.parse?

A newline between tokens is fine — JSON ignores whitespace there. The problem is a newline inside a string value, between the quotes. JSON strings may not contain a literal newline; the line break must be written as the two-character \n escape. A real line break in the source (often from a template literal or copy-pasted text) is what triggers Bad control character.

What is the Python equivalent of this error?

Python's json module raises json.decoder.JSONDecodeError: Invalid control character at: line X column Y (char Z) for the same problem — a raw control character inside a string. The fix is identical: escape the character, or build the JSON with json.dumps() instead of string concatenation. Python also offers json.loads(text, strict=False) to tolerate literal control characters if you cannot fix the source.

About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.