Encoding / Decoding

Punycode Encode / Decode

Convert domain names to/from Punycode (IDN).

Unicode Domain
Punycode Domain

What is Punycode?

Punycode is an encoding used to represent Unicode characters in ASCII for internationalized domain names (IDN). It converts non-ASCII domain names to ASCII-compatible encoding.

Use Cases

  • Internationalized domain names
  • Email address encoding
  • URL handling with non-ASCII characters

Punycode converts every domain label containing characters outside ASCII into the RFC 3492 form prefixed with xn--, and back again. It is used when configuring DNS records, TLS certificates, mail domains or CDN rules that accept only ASCII labels, and when checking what an xn-- hostname actually spells.

Encoding runs per label, not per string

Every label (one dot-separated segment) that stays inside U+0000–U+007F is copied through untouched, so 中国.cn becomes xn--fiqs8s.cn and the .cn suffix is never rewritten. Inside a label, ASCII basic code points are kept verbatim at the front and separated from the encoded tail by a single -: münchen gives mnchen-3ya, whereas ü alone has no basic code points and yields tda with no delimiter at all. Separators U+002E, U+3002 (。), U+FF0E (.) and U+FF61 (。) are normalized to an ASCII dot before the split, and when the input looks like a mail address, everything before @ is left alone.

The bootstring parameters behind the tail

The tail is a bootstring integer sequence fixed by RFC 3492: base 36, digits a–z for values 0–25 followed by 0–9 for 26–35, starting code point n = 128 (U+0080), initial bias 72, damp 700, skew 38, thresholds tmin 1 and tmax 26. Encoding emits one delta per character — the distance to the next code point plus the shift caused by characters already inserted — so editing a character usually rewrites the entire tail instead of swapping one symbol: 中文 is xn--fiq228c while 中国 is xn--fiqs8s, and only the shared fiq survives the change. A decoder must run the identical parameter set, which is why mismatched tables produce garbage or invalid-input errors.

The output is raw Punycode, not a valid A-label

This is the bare RFC 3492 codec, so it performs no IDNA processing. There is no NFC normalization, no case folding and no UTS-46 mapping table: Bücher.example yields xn--Bcher-kva.example with the capital B carried into the label, while the IDNA-normalized A-label is xn--bcher-kva.example. Precomposed and decomposed spellings also diverge, because café (U+00E9) encodes to xn--caf-dma but cafe plus U+0301 encodes to xn--cafe-yvc. The 63-octet DNS label limit is not checked either: sixty ü characters produce a 66-character label that no resolver will accept. Decoding recognizes only a lowercase xn-- prefix, so XN--fiqs8s.cn is returned unchanged.

Worked example

Encoding a domain typed with a full-width full stop

Input
中文。中国
Output
xn--fiq228c.xn--fiqs8s

The ideographic full stop U+3002 is rewritten to an ASCII dot before the split, so the result is two encoded labels, each carrying its own xn-- prefix. Neither label holds ASCII basic code points, so no - delimiter follows the prefix — the only hyphen is the one inside xn--. Because the tail stores delta positions rather than a per-character substitution, changing the second character rewrites it completely: 228c for 文 becomes s8s for 国.

Limits and pitfalls

  • Input is trimmed first, so outer whitespace vanishes; a space left inside a label is treated as an ordinary ASCII character and encoded into the label, so 中 国.cn becomes the unregistrable xn-- -kq6az5z.cn.
  • Decoding only matches a lowercase xn-- prefix: XN--fiqs8s.cn and unprefixed labels come back unchanged, which looks like the tool did nothing.
  • Malformed payloads such as xn--!!!! throw an internal RangeError that the tool swallows into an empty result, so a bad payload is indistinguishable from empty input.
  • No normalization is applied: precomposed and decomposed forms of one name encode to different A-labels, so run NFC first when comparing against registry data.
  • Labels longer than the 63-octet DNS limit are emitted anyway — sixty ü characters give a 66-character label that only fails later at registration or resolution.

Related tools

Where this codec stops and UTS-46 takes over

SituationThis page: raw RFC 3492Alternative
The name has capitals in it, or a phone autocapitalised the first letterNothing is folded: Bücher.example encodes to xn--Bcher-kva.example, because the uppercase letter is left standing in the basic prefix and only the encoded tail is computed over the exact code points you typed.A URL parser runs UTS-46 first: new URL("https://Bücher.example").hostname and Node's url.domainToASCII both return xn--bcher-kva.example. Use them when the string has to match a registry record or a certificate; stay here when you want the codec's raw output for the bytes you actually typed.
Decoding a hostname that reached you in a report as XN--fiqs8s.cnThe prefix test is a literal lowercase match, so nothing is decoded: the output equals the input and the page looks broken rather than wrong.Lowercase it first — XN--fiqs8s.cn → xn--fiqs8s.cn → 中国.cn — or hand the whole name to url.domainToUnicode, which accepts either case and answers 中国.cn in one call.
You pasted a full address instead of a hostnamehttps://中国.cn comes back as xn--https://-sd0mx14e.cn: RFC 3492 treats : and / as ordinary basic code points, and the codec has no concept of a scheme to strip.Let the URL parser split it — new URL("https://中国.cn").hostname gives xn--fiqs8s.cn. In practice: copy just the hostname out of the address bar and keep this page for the label alone.
Asking whether the label will survive DNS at allNo length or character validation is done: 57 copies of 中 encode to a 63-character label, 58 copies to 64, and both are printed without a word of warning.Send the result to a real resolver — dig +short xn--fiqs8s.cn — or let a registrar's availability check judge it; the 63-octet label limit from RFC 1035 takes effect there, not here and not in domainToASCII.
Two hundred domains sitting in a CSVOne paste yields one output, with no list input and no record of what you converted five minutes ago.Loop url.domainToASCII in Node: it hands back an empty string for a failing name, which is the only failure signal it gives. Write out the non-empty results and spot-check a few rows here before trusting the batch.

One concrete scenario

One domain into an ASCII-only DNS field

The registrar's host field takes ASCII only, and the name you are moving is 中文网.中国: six code points, 16 UTF-8 bytes counting the dot as one. Encoding yields xn--fiq228c5hs.xn--fiqs8s — 25 ASCII characters in two labels of 14 and 10, both far below the 63-octet ceiling, so size is not a factor at this scale. Type the name in lowercase first: DNS ignores case, but the allowlist you are about to edit compares characters, and this page preserves whatever you typed. Then hit ⇄ once — it moves the output into the input and flips to decode — and confirm the label returns as exactly 中文网. If a capital or a different character shows up, fix the input, not the output.

Frequently Asked Questions

Why do some web addresses start with xn--?
That is the marker for internationalized domain names: the DNS only supports ASCII, so Chinese, Japanese, and other non-ASCII domains are stored and resolved in an xn-- encoded form. For example, the Chinese domain equivalent of example.com becomes xn--fiq228c.com. Browsers display the original script but request the punycode form.
Which form should I use when registering a domain?
Registrars usually accept both, but WHOIS records and DNS configuration store the xn-- encoded form. When setting up DNS records, CDN rules, or TLS certificates, always confirm whether a system expects the encoded or Unicode form — mixing them is a classic source of outages.
Are there security risks with punycode?
Yes — homograph attacks: registering look-alike domains by substituting Cyrillic а for Latin a. Modern browsers show the raw punycode for high-risk combinations. Pasting any xn-- domain into this decoder is a quick way to verify its true spelling.

Content reviewed: