url.domainToASCII()

Converts an internationalized domain name to its Punycode ASCII form, or '' if it is invalid.

Signature
url.domainToASCII(domain)
  • Added in Node.js v7.4.0, v6.13.0

Parameters

ParameterTypeDescription
$domainstringThe domain name to convert. May already be ASCII, in which case it is returned normalized.

Return value

stringThe Punycode ASCII serialization of the domain. If domain is an invalid domain, the empty string is returned.

Errors

Does not throw for an invalid domain; it returns the empty string instead.

Changelog

  • PHP v20.0.0, v18.17.0The ICU requirement was removed, so conversion now works on small-ICU builds as well.

domainToASCII() applies the IDNA/UTS-46 rules that turn a Unicode domain such as español.com into its ASCII-compatible encoding xn--espaol-zwa.com. Every label is converted independently and ASCII labels pass through, lowercased. url.domainToUnicode() performs the inverse.

When to reach for it

Reach for it when you have a bare hostname rather than a full URL — a value from a config file, a Host header, an MX record, an allowlist entry — and you need it in the canonical ASCII form that DNS, TLS certificates and comparisons use. If you already have a URL, new URL(str).hostname has done this for you as part of parsing.

It is also a cheap validity screen: an empty return means the string is not a usable domain at all.

What trips people up

The conversion is purely local — a table lookup and some string transformation. No DNS query is made, nothing is contacted, and a successful conversion says nothing about whether the domain is registered or resolvable.

Failure is silent. Invalid input returns '', not null and not an exception, so domainToASCII(userInput) === userInput is a poor validity test and an unchecked result can flow into a URL as an empty host. Always compare against '' explicitly.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Normalize hostnames from a config file before comparing them to an allowlist
  • Convert a user-typed internationalized domain into the form DNS and TLS expect
  • Screen out obviously malformed hostnames before building a URL
  • Render a stored xn-- hostname back to Unicode for display using domainToUnicode

Frequently asked questions

Does url.domainToASCII perform a DNS lookup?

No. It is a pure local string transformation using the IDNA rules, with no network access at all — a converted domain may not exist or resolve.

Why does domainToASCII return an empty string?

Because the input is not a valid domain: it contains a space or another disallowed character, has an empty or malformed label, or is itself empty. It signals failure by returning '' rather than throwing, so check for '' explicitly.

Do I need domainToASCII if I already parse the URL?

No — new URL() converts the hostname to Punycode as part of parsing, so url.hostname is already in ASCII form. domainToASCII is for bare hostname strings that never go through the URL parser.

More examples

Normalizing hostnames before an allowlist check

Both the allowlist and the input go through the same conversion, so the Unicode spelling, the uppercase spelling and the stored xn-- form all match. The explicit '' check is what stops a malformed hostname from being compared as-is.

Example · javascript
const url = require('node:url');

const allowlist = new Set(['example.com', 'xn--espaol-zwa.com'].map((h) => url.domainToASCII(h)));

function allowed(host) {
  const ascii = url.domainToASCII(host);
  if (ascii === '') return false;
  return allowlist.has(ascii);
}

console.log(allowed('español.com'));
console.log(allowed('ESPAÑOL.COM'));
console.log(allowed('evil.example'));
console.log(allowed('not a domain'));

Detecting invalid domains

Labels containing a space, a leading hyphen, or an empty segment all fail and return ''. An IPv4 literal is a valid host and comes back unchanged, which is worth knowing if your allowlist is meant to cover names only.

Example · javascript
const url = require('node:url');

const candidates = ['ok.example', 'has space.com', '-lead.example', 'a..b.com', '192.168.0.1'];

for (const c of candidates) {
  const ascii = url.domainToASCII(c);
  console.log(c.padEnd(16), ascii === '' ? 'INVALID' : ascii);
}

Round-tripping for display

Store and compare the ASCII form; render the Unicode form only in a UI. The round trip is lossless here, but never make a security decision on the Unicode string — visually identical characters from different scripts encode to different xn-- labels.

Example · javascript
const url = require('node:url');

const typed = 'пример.example';
const stored = url.domainToASCII(typed);

console.log('store this: ', stored);
console.log('show this:  ', url.domainToUnicode(stored));
console.log('round trip ok:', url.domainToUnicode(stored) === typed);
console.log('display form re-encodes:', url.domainToASCII(url.domainToUnicode(stored)) === stored);

Discussion

  • Be the first to comment on this lesson.