Encoding / Decoding

Unicode Escape

Convert text to/from Unicode escape sequences (\uXXXX).

Original
Escape result

What is Unicode Escape?

Unicode escape represents characters as \uXXXX (UTF-16) or \UXXXXXXXX (code point) format, useful for representing non-ASCII characters in code, regex, or protocols.

Use Cases

  • Unicode characters in regular expressions
  • Non-ASCII strings in APIs or config files
  • XSS prevention and special character filtering

Unicode escape rewrites a string as backslash-u sequences: four zero-padded hex digits per UTF-16 code unit, or eight digits in the capital-U style that C and Python reserve for code points. It converts in both directions and is meant for carrying non-ASCII text through ASCII-only configs and source files, and for making invisible characters visible.

One escape per UTF-16 code unit

The encoder walks the string with split(''), which yields UTF-16 code units rather than code points, and writes \u plus four lowercase hex digits for each. The engine has already split astral characters before the loop starts, so 😀 arrives as 0xD83D and 0xDE00 and both halves satisfy the four-digit condition. The surrogate arithmetic inside the function — high = 0xD800 + (cp − 0x10000) / 0x400, low = 0xDC00 + (cp − 0x10000) % 0x400 — therefore never runs in this mode. Spaces, tabs, newlines, ASCII letters and CJK characters are all expanded, and the result is exactly six characters per code unit: length 6 × the input's length, never a mix of escaped and literal text.

The eight-digit option does not write code points

Picking the capital-U format keeps the same per-code-unit loop but reads each unit with codePointAt(0). On a lone surrogate that call returns the surrogate value itself, so 😀 is emitted as \U0000d83d\U0000de00: two surrogates dressed up as code points. Even the maximum scalar value U+10FFFF comes out as \U0000dbff\U0000dfff, and no eight-digit value this mode can produce exceeds 0xDFFF. C++ and Go read \U0001F600 as a single code point, and a surrogate value is not a code point at all, so the padded pair is rejected instead of read as 😀. Decoding is asymmetric: a hand-typed \U0001F600 does return 😀 through String.fromCodePoint, so the mode reads correct input it cannot itself reproduce. Only BMP text survives a round trip here.

Decoding is find-and-replace, not parsing

Decode runs two global patterns in order: \u[0-9a-fA-F]{4} through String.fromCharCode, then \U[0-9a-fA-F]{8} through String.fromCodePoint. Everything the patterns miss is copied through untouched, so the ES2015 braced form \u{1F600}, a three-digit fragment such as \u4e2, and JavaScript's own \x41 or \n escapes stay as literal text instead of raising an error. Valid surrogate pairs recombine because fromCharCode rebuilds the string unit by unit. A stray \uD800 becomes an unpaired surrogate: legal inside a JavaScript string, but not representable in UTF-8, so it turns into U+FFFD as soon as the text is saved or handed to another tool.

Worked example

Encoding ASCII, an accented letter and an emoji

Input
café 😀
Output
\u0063\u0061\u0066\u00e9\u0020\ud83d\ude00

Six code points but seven UTF-16 code units: é takes one unit (00e9) while 😀 takes two, so the output is 42 characters (7 × 6). Lowercase hex with four-digit padding is why c is written \u0063 rather than \u63. The eight-digit mode gives 70 characters for the same input and ends in \U0000d83d\U0000de00.

Limits and pitfalls

  • Nothing is passed through: an A becomes \u0041, so output is six times the input's length and there is no mode that escapes only non-ASCII characters.
  • The capital-U form is not valid inside a JavaScript string literal, and Rust and PHP want the braced \u{...} form, so output pasted into the wrong language fails at parse time rather than producing wrong text.
  • Only exactly four or eight hex digits are recognised; \u{1F600} and \x41 pass through unchanged, so mixed payloads decode only partially and silently keep the original escapes.
  • Hand-written surrogate pairs must be ordered and complete: \uDE00\uD83D or a lone \uD83D decodes to an unpaired surrogate that breaks on the next UTF-8 encode.
  • Re-encoding the tool's own output escapes the backslashes too — \u4e2d becomes \u005c\u0075\u0034\u0065\u0032\u0064 — so encode twice and the text is no longer decodable back to the original.

Related tools

Six destinations where the four-digit output is the wrong dialect

DecisionUnicode Escape (this page)The alternative
The escaped string is going into a JSON documentThe page writes bare escapes, never a quoted string: a double quote comes out as \u0022 and a newline as \u000a, so whatever surrounds it stays your job — pick it only when the receiver is a language literal, not a JSON parser.The JSON string escape tool leaves everything above U+007F literal (中文 stays 中文) and uses the short forms \n, \", \t; choose it when the payload is JSON and a human still has to read the diff.
The value is rendered as HTML text or an attribute valueA browser has no rule for \u4e2d, so it paints backslash-u-4-e-2-d on the page; these escapes are understood by language parsers only, which makes the page useless for markup.The HTML entity tool escapes just &, <, >, the two quote characters and space in default mode, and its full mode writes decimal entities such as &#20013; — take that route whenever the destination is a document.
The string is part of a URL, a query value or a form fieldThe output says nothing about UTF-8 bytes, so a URI parser passes the backslash, the u and the four digits straight through and the server receives the escape text.The URL encode tool calls encodeURIComponent, turning 中 into %E4%B8%AD; choose it for anything a URI parser will decode, and keep this page for source code.
Only the non-ASCII characters should be escapedThere is no switch for that. Encoding a key such as id_ plus 中 expands the ASCII too, and the part that made the line readable is gone.Two lines of Node are exact: match /[^\x00-\x7F]/g and replace each hit with a backslash, a u and four hex digits from charCodeAt(0); use it when the surrounding keys or log prefixes must stay readable.
You are trying to identify a character rather than transport itEvery code unit collapses into an opaque four-digit number with no name and no decimal form, so \u00e9 tells you nothing about the letter until you decode it and look again.The ASCII / code point lookup page shows the decimal value and the class label side by side; use it to audit a suspicious string, then come back here only to produce the escape you actually ship.
The input is a file, or a few hundred stringsOne textarea, one buffer, no file picker and no batch mode — a whole locale file has to be pasted and copied in a single pass, and the encoding repeats only as often as you click.Push the conversion into the build: python3 -m json.tool emits four-digit escapes for every non-ASCII character by default, and a short Node script over fs.readFileSync covers non-JSON files; choose it when the same conversion must run identically on every build.

What each kind of text costs: UTF-16 code units in, escape characters out

TextUTF-16 code units\u mode output\U mode output
Space, U+00201610
Line feed, U+000A1610
Backslash, U+005C1610
中, U+4E2D1610
é, U+00E9 (one code point)1610
e + U+0301 (two code points, same visible glyph)21220
😀, U+1F60021220
🇨🇳 (two regional indicators)42440
👨‍👩‍👧 (three emoji joined by two zero-width joiners)84880

The unit count is what length reports and what the encoder walks, so it follows UTF-16 rather than visible characters: precomposed é is one unit while the decomposed e + U+0301 is two. Each unit is padded to four or eight hex digits, which is where 6 and 10 come from. Nothing in the tool truncates, wraps or abbreviates the output, so these numbers are exact for any input length.

Frequently Asked Questions

What is the relationship between \uXXXX escapes and UTF-8 bytes?
\uXXXX is the hexadecimal form of a UTF-16 code unit, not a UTF-8 byte. Characters in the BMP map one-to-one, but characters outside it (most emoji) are surrogate pairs: 😀 is \uD83D\uDE00. This tool shows the code point (U+1F600) and the UTF-8 bytes together so the two concepts stay distinct.
Why does an emoji become two \u escapes?
JavaScript strings are UTF-16: characters beyond U+FFFF require a pair of surrogate code units. ES6 added the \u{1F600} braced syntax that writes by code point directly, and this tool can output either format.
When do I actually need Unicode escapes?
Typical cases: carrying text through configs or protocols that only accept ASCII; debugging strings that look identical but compare unequal (invisible characters, full-width versus half-width); reading console output full of \u sequences. Escaping makes invisible differences visible.

Content reviewed: