Unicode Converter

Encode text to codepoints or decode them back

This page used to be two separate tools; the encode and decode directions are now two modes of the same page, since anyone debugging a Unicode problem usually needs both within the same minute. Characters → Unicode takes text and returns one row per code point — the character, its decimal value, its U+ form, a JavaScript escape, and an HTML entity, with copy buttons for each format. Unicode → Characters does the reverse: paste anything containing recognisable tokens (U+1F600, \u0048, 😀, 0x41, or bare decimals) and get the decoded text back, with everything else in the input left untouched.

"One row per code point" in encode mode is the detail to hold onto, because it is not one row per character you can see. Paste the US flag emoji and you get two rows — U+1F1FA and U+1F1F8, the regional indicator letters U and S. Paste a family emoji and you get seven. This is precisely why it is the right tool for the bug where two strings look identical on screen and refuse to compare equal.

Code point, code unit, grapheme cluster

Three different things get called "a character", and almost every string bug lives in the gap between them.

  • A code point is a number from 0 to 0x10FFFF that Unicode assigns to an entity. That is what the encode table lists, one per row.
  • A code unit is how that number is stored. JavaScript strings are UTF-16, so anything above U+FFFF takes two 16-bit units — a surrogate pair. "😊".length is 2, and the pair is 0xD83D followed by 0xDE0A, but the encode table shows a single row, U+1F60A, because it iterates code points rather than units.
  • A grapheme cluster is what a reader calls a character. Its boundaries are defined by UAX #29, and it can be many code points long.

The flag emoji makes all three visible at once. "🇺🇸".length is 4 in JavaScript: two code points, each stored as a surrogate pair, rendering as one grapheme. The four-person family emoji is worse — 11 code units, 7 code points, one grapheme. In decode mode, Character Count reports the code-point figure (7), not the JavaScript .length figure — that gap is exactly the bug behind truncated text and half-drawn emoji.

Reference: invisible troublemakers, and accepted decode tokens

CharacterCode pointHow it shows up as a bug
No-break spaceU+00A0Looks like a space; survives trim(), defeats split(' ').
Zero-width spaceU+200BInvisible, but counts toward length and breaks equality.
Zero-width joinerU+200DGlues emoji into one glyph; why a "one character" limit rejects a family emoji.
Byte-order markU+FEFFLeads a CSV exported from a spreadsheet, so the first header never matches.
Cyrillic small aU+0430A perfect homoglyph for Latin a (U+0061) — the basis of lookalike domain attacks.
Right single quotationU+2019What a word processor substitutes for an apostrophe; breaks naive string comparisons.

Decode mode accepts U+00A9, \u00A9 (exactly four hex digits), \u{1F600}, ©, ©, 0xA9, and bare decimals — but not named HTML entities like ©, CSS escapes, C-style \x41, or percent-encoding.

How each direction actually works

Encoding: the text is walked with for (const ch of text), which yields code points rather than code units, so a surrogate pair arrives as one item and is never split. Each item becomes decimal (ch.codePointAt(0)), hex (U+0041, padded to four digits), a JS escape (\u00A9 at or below U+FFFF, the braced \u{1F60A} above it), and a numeric HTML entity (© — never a named entity, since numeric ones are understood everywhere and named ones outside a small core set are not).

Decoding: one regular expression scans for all seven token forms at once. Each hit is parsed in the appropriate base, range-checked against 0–0x10FFFF, and passed to String.fromCodePoint(n) — deliberately not fromCharCode, which silently truncates values above 0xFFFF instead of building the surrogate pair. A token outside the valid range is left in the output as literal text rather than throwing.

Worked examples, one per direction

Encoding Café 🎉 (with a precomposed é) gives six rows, ending in é = U+00E9 and the party popper at U+1F389. Retype the é as a plain e plus a combining acute accent and the table grows to seven rows — same rendering, different code points, and === returns false between the two strings. That is the entire mechanism behind a whole genre of "the search finds nothing" reports.

Decoding \u0048\u0069 U+1F600 ! decodes to Hi 😀 ! with a Character Count of 6. The trap: paste Order 42 shipped and you get back Order * shipped, because a bare 42 is treated as a decimal code point (U+002A is an asterisk). If your input has ordinary numbers in it, prefix your real codes with U+ or 0x so the parser cannot confuse the two.

Normalization, bytes, and named entities

NFC and NFD. UAX #15 normalization forms turn canonically equivalent text into a single representation. café in NFC is four code points ending in U+00E9; in NFD it is five, ending in U+0065 U+0301. Call .normalize('NFC') on both sides before comparing, and store one form consistently.

Code points are not bytes. U+1F389 is one code point, two UTF-16 code units, and four UTF-8 bytes. If you are sizing a database column or a byte-based length limit, none of the figures on this page is the number you want — see the Data Storage Converter for that arithmetic.

Only numeric entities decode. ©,   and & pass through decode mode unchanged — convert them to numeric form first, or look them up in encode mode.

An empty box is not an error in either mode. The code point is correct; your installed fonts simply have no glyph for it.

Diagnosis, escaping, and reading escaped API output

Diagnosis: paste a value that will not match, or two strings that look identical but fail an equality check, into encode mode and read down the hex column — the offending invisible character is usually sitting there in plain sight.

Escaping for source: the Copy JS Escapes and Copy HTML Entities buttons hand back a string safe to embed in code, a minifier, or template HTML.

Reading escaped API output: a JSON response can escape any non-ASCII character, so an ordinary name like “José” arrives in a log as Jos\u00E9. Paste the whole line into decode mode — the surrounding JSON passes through untouched and only the escape resolves.

If the string you are debugging is being matched by a pattern, the Regex Tester pairs well with this tool, since invisible characters are a common reason a pattern fails on real input.

Frequently asked questions

Why does one emoji produce several rows in encode mode?

Because the table lists code points, and many emoji are sequences. A flag is two regional indicator letters, a family is several people joined by zero-width joiners. The rendered glyph is one grapheme cluster; the underlying code points are several.

Two strings look identical but do not compare equal. What do I look for?

Run both through encode mode. The usual culprits are a no-break space where you expected U+0020, a zero-width space or byte-order mark, a smart quote instead of an apostrophe, or the same accented letter in composed and decomposed form.

Why did the numbers in my sentence turn into symbols in decode mode?

Bare decimals up to seven digits are treated as code points, so "Order 42" decodes 42 to an asterisk. Prefix your real codes with U+ or 0x so the parser can't confuse them with ordinary numbers.

Why is "🇺🇸".length equal to 4 in JavaScript?

Two code points, each above U+FFFF and therefore stored as a surrogate pair of two UTF-16 code units. JavaScript's length counts code units, not code points and not graphemes — which is also why decode mode's Character Count can differ from .length.

Why numeric HTML entities instead of names like ©?

Numeric entities work everywhere. The named set is large in HTML but tiny in XML, so © is portable in a way © is not — and it's also why decode mode only recognises the numeric form.

What happens to an out-of-range value like 0x110000 in decode mode?

It is left in the output as literal text. Valid code points stop at U+10FFFF, and the tool range-checks before calling fromCodePoint rather than letting one bad token break the whole result.

Sources and further reading

Figures and definitions on this page are drawn from the following primary sources. If you find something out of date, tell us and we will correct it.

Related tools