npm.io
17.0.3 • Published yesterday

idn-hostname

Licence
MIT
Version
17.0.3
Deps
1
Size
215 kB
Vulns
0
Weekly
0

title: IDN Hostname description: "A validator for Internationalized Domain Names (IDNA2008) with nontransitional UTS #46 preprocessing."


IDN Hostname

idn-hostname validates internationalized hostnames and converts valid input to ASCII Compatible Encoding (ACE). It combines:

The bundled table targets Unicode 17.0.0. The package is CommonJS and depends on punycode. Browser use requires a bundler or runtime that supports those modules; the package does not declare a browser compatibility guarantee.

The supported Node.js range is >=24.13.1 <25 || >=26.0.0. Node.js 24.13.1 is the first LTS release in the 24.x line to bundle ICU 78.2; ICU 78 updates Unicode properties, normalization data, and IDNA data to Unicode 17. Node.js 24.13.0 and earlier use ICU 77.1, and Node.js 25 also uses ICU 77.1, so those runtimes cannot provide the Unicode 17 behavior required by this release. Node.js 26 starts with ICU 78.3 and is compatible. The package bundles its own UTS #46 table, but its normalization and Unicode-property checks still require matching runtime Unicode data.

Install

npm install idn-hostname@17

API

Validate a hostname

isIdnHostname(hostname) returns true or throws a SyntaxError object with a specific error name at the first detected violation.

const { isIdnHostname } = require('idn-hostname');

try {
    isIdnHostname('mañana.example');
    console.log('valid');
} catch (error) {
    console.error(error.name, error.message);
}
Convert a hostname to ACE

idnHostname(hostname) validates the input and returns its ASCII form.

const { idnHostname } = require('idn-hostname');

try {
    console.log(idnHostname('mañana.example'));
    // xn--maana-pta.example
} catch (error) {
    console.error(error.name, error.message);
}
Apply UTS #46 preprocessing to one label

uts46map(label) applies the package's nontransitional, STD3-disabled preprocessing policy. It does not perform NFC normalization or the complete final IDNA2008 validation.

const { uts46map } = require('idn-hostname');

try {
    const label = uts46map('ABC').normalize('NFC');
    console.log(label); // abc
} catch (error) {
    console.error(error.name, error.message);
}
Access the Punycode dependency

The exported punycode object is the low-level dependency, not a replacement for IDNA validation. Use idnHostname when converting a hostname for use.

const { punycode } = require('idn-hostname');

console.log(punycode.toUnicode('xn--maana-pta')); // mañana

Processing model

The validator uses a narrow ASCII fast path and otherwise deliberately separates compatibility preprocessing from final eligibility. Before entering the Unicode pipeline, isIdnHostname directly accepts a non-reserved ASCII LDH hostname when:

  • every label contains only ASCII letters, digits, and internal hyphens;
  • every label contains 1–63 characters;
  • no label contains -- in the third and fourth positions;
  • the complete presentation form contains at most 253 characters.

Reserved labels, including labels beginning with xn--, continue through the complete IDNA validation pipeline. idnHostname still applies UTS #46 mapping when producing its result, so uppercase ASCII input is returned in lowercase. Inputs that do not meet the fast-path constraints follow these steps:

  1. Split the hostname at UTS #46 label separators and reject empty labels, including a trailing root label.
  2. Apply nontransitional UTS #46 mappings with STD3 rules disabled.
  3. Normalize ordinary Unicode input to NFC.
  4. Recognize and decode an ACE prefix after preprocessing, so case and compatibility mappings cannot hide or create an unchecked prefix.
  5. Require a decoded A-label to already be NFC; decoded Punycode is not remapped or normalized into validity.
  6. Check every resulting code point against the final/default IDNA table.
  7. Apply hyphen, leading-mark, CONTEXTJ, CONTEXTO, bidi, ACE round-trip, and DNS length checks.

This order permits sequences such as Hangul Jamo to compose into an eligible syllable during NFC while still rejecting mapped output that remains ineligible.

Complete validation pipeline

For each hostname, the implementation:

  1. Requires a JavaScript string.
  2. Accepts a hostname satisfying the non-reserved ASCII LDH fast-path constraints without Unicode table, contextual, Punycode, or bidi validation.
  3. For remaining input, splits labels on U+002E, U+FF0E, U+3002, and U+FF61.
  4. Rejects leading, trailing, or consecutive separators because they create an empty label.
  5. Determines whether the hostname requires RFC 5893 bidi enforcement.
  6. Preserves the existing raw non-ASCII A-label syntax check before preprocessing.
  7. Applies uts46map and NFC to ordinary input.
  8. If the preprocessed label starts with xn--:
    • requires ASCII input;
    • decodes the Punycode payload;
    • rejects an empty or all-ASCII decoded U-label;
    • requires the decoded U-label to already be NFC;
    • requires exact decode/re-encode agreement after preprocessing has normalized the ACE label's case.
  9. Converts the resulting label to ASCII for length accounting and requires at most 63 ASCII octets.
  10. Rejects a leading or trailing hyphen and hyphens in positions 3 and 4 of a U-label.
  11. Rejects a leading Unicode mark.
  12. Spreads the label into code points once and uses the existing per-code-point loop to:
    • require final valid or nontransitional deviation eligibility;
    • enforce CONTEXTJ and CONTEXTO rules;
    • enforce RFC 5893 bidi rules when the hostname is bidi.
  13. Requires the complete ASCII presentation form, without a trailing root dot, to contain at most 253 octets.

The 63-octet label limit and 255-octet DNS wire-format limit come from the DNS size limits described by RFC 1035 and RFC 5890. The implementation's 253-character presentation-form limit accounts for separators while intentionally disallowing a trailing root dot.

Enforced label rules

Hyphen, normalization, ACE, and leading-mark rules
  • A label cannot begin or end with U+002D HYPHEN-MINUS.
  • A U-label cannot contain hyphens in positions 3 and 4.
  • A label cannot begin with a Unicode character in General Category M.
  • An apparent A-label must contain only ASCII after preprocessing and must decode successfully.
  • A decoded A-label must produce a non-ASCII U-label in NFC and round-trip through Punycode.
  • Final code points must be eligible after preprocessing and normalization.

References: RFC 5890 §2.3.2.1; RFC 5891 §4.2.3 and §5.4; UTS #46 §4.1.

CONTEXTJ and CONTEXTO rules

The validator implements checks for every RFC 5892 Appendix A contextual-rule family, including CONTEXTO checks that lookup-only applications are not universally required to evaluate:

  • U+200C ZWNJ: accepted after a virama or when its joining-type context satisfies RFC 5892 Appendix A.1.
  • U+200D ZWJ: accepted only immediately after a virama.
  • U+00B7 MIDDLE DOT: accepted only between two lowercase ASCII l characters after preprocessing. An uppercase L in ordinary input is mapped to lowercase before this check.
  • U+0375 GREEK LOWER NUMERAL SIGN: must be followed by a Greek-script character.
  • U+05F3/U+05F4 Hebrew punctuation: must follow a Hebrew-script character.
  • U+30FB KATAKANA MIDDLE DOT: requires at least one Hiragana, Katakana, or Han character in the label.
  • Arabic digit sets: U+0660–U+0669 and U+06F0–U+06F9 cannot occur together in one label.

Reference: RFC 5892 Appendix A.1–A.9.

Bidirectional-text rules

If the hostname contains an RTL label, every label is checked under RFC 5893:

  • an RTL label must begin with R or AL, use only the permitted classes, and end with R, AL, EN, or AN, followed by optional NSM characters;
  • an RTL label cannot mix EN and AN digits;
  • an LTR label must begin with L, use only the permitted classes, and end with L or EN, followed by optional NSM characters.

Reference: RFC 5893 §2.

Unicode data

The deployment JSON is generated directly from authoritative Unicode 17.0.0 text files:

IdnaMappingTable.txt is a UTS #46 data file, not the RFC 5892 derived-property table. The generator uses its NV8 and XV8 markers to keep preprocessing permission separate from final IDNA2008 eligibility.

The validator ships one table and does not select a Unicode version at runtime.

Compact JSON schema and current counts

idnaMappingTableCompact.json contains:

Member Purpose Unicode 17.0 count
props Property names: valid, mapped, deviation, ignored, disallowed 5
ranges Final/default property ranges 2,078
uts46_ranges Preprocessing overrides used only when UTS #46 differs from ranges 468
mappings Non-empty preprocessing mappings keyed by source code point 6,377
viramas Code points with canonical combining class 9 69
bidi_ranges Explicit bidi-class ranges 1,611
joining_type_ranges Explicit joining-type ranges 529

During preprocessing, uts46_ranges takes precedence and lookup falls back to ranges. After mapping and NFC—or after unmodified ACE decoding—final validation consults only ranges and accepts valid or deviation.

Missing final/default ranges are treated as disallowed or unassigned. Deviation mappings are omitted because processing is nontransitional and leaves deviation code points unchanged.

Errors

The API stops at the first fatal violation. Each validator function (isIdnHostname, idnHostname, and uts46map) may throw a SyntaxError object with one of these names:

Error names and responsibilities
Error name Responsibility
IdnaUnicodeError Disallowed, unassigned, or finally ineligible code point
IdnaSyntaxError General label syntax, normalization, mark, or hyphen failure
IdnaLengthError Empty label or DNS label/hostname length failure
IdnaContextJError ZWNJ or ZWJ contextual failure
IdnaContextOError Other RFC 5892 contextual failure
IdnaBidiError RFC 5893 bidi failure
PunycodeError ACE decoding, re-encoding, or ASCII conversion failure

Messages include the relevant RFC or UTS reference. Error precedence follows the validator's processing order and is part of its current behavior.

Intentional policy and limitations

  • Processing is nontransitional; the deprecated transitional mappings are not offered.
  • STD3 ASCII rules are disabled during preprocessing. This does not make otherwise ineligible code points valid after NFC.
  • The validator rejects a trailing root dot instead of accepting an absolute/FQDN presentation form.
  • It evaluates every implemented CONTEXTO rule, including during lookup-style use.
  • It performs no locale-specific casing or registration-policy processing.
  • It does not query DNS, determine whether a name is registered, or apply registry-specific script, confusability, or security policies.
  • Validation establishes conformance with this implementation's syntax and policy; it does not guarantee that a browser, resolver, registry, or registrar will accept the name.

Examples

Valid examples
[
    'example',
    'sub.example',
    'mañana',
    'xn--maana-pta',
    'bücher',
    'пример.рф',
    'مثال',
    'דוגמה',
    '例子',
    'l·l',
    'L·l',        // uppercase ASCII is mapped to lowercase first
    'क्‍ष',       // virama + ZWJ
    'क्‌ष',       // virama + ZWNJ
    'a⁠b',        // U+2060 is ignored during preprocessing
    '가',        // Hangul Jamo normalize to 가
]
Invalid examples
[
    '',
    '.example',
    'example.',       // trailing root labels are intentionally rejected
    'a..b',
    '-abc',
    'abc-',
    'a b',
    'emoji😀',
    'a·l',            // U+00B7 is not between two l characters
    'a‌',              // ZWNJ lacks a valid context
    'a‍',              // ZWJ is not preceded by a virama
    '̀hello',          // begins with a combining mark
    '٠۰',              // mixes the two Arabic digit sets
    '¼',               // maps through finally ineligible U+2044
]

Some examples contain invisible format characters. Keep source encoding intact when copying them.

Verification

Tests and benchmarks are maintained in SorinGFS/public-data rather than in the package or canonical repository. The gh-workspace-data extension materializes those concerns together with the shared #/version-layers.js runtime required by both dispatchers.

gh-workspace-data usage

Install the GitHub CLI extension once:

gh extension install SorinGFS/gh-workspace-data

Initialize and load workspace data from the cloned project repository:

gh workspace-data init
gh workspace-data load

The extension materializes ordinary local files under #/public/tests/ and #/public/benchmarks/, while #/version-layers.js provides deterministic version-layer discovery. The generated #/ namespace remains excluded from the canonical Git repository and npm package.

Run gh workspace-data load again to refresh materialized data after public-data changes or an extension upgrade.

Tests

Package fixtures remain in delta-only Unicode scopes: v15.1 contains 108 cases, v16.0 contains seven additions, and v17.0 contains seven additions. Because #/public/tests/index.json marks the validation callback as backwards compatible, the dispatcher runs numeric fixtures from every version layer not newer than the installed package while keeping explicit conformance concerns exact-scoped. For the current 17.0 release line, the active suite contains 6,324 independently reported tests: all 122 eligible package fixtures and all 6,202 applicable nontransitional Unicode 17.0.0 IdnaTestV2.txt vectors.

Test details

Install package dependencies and run the materialized suite:

npm install
npm test

The suite uses the node:test module built into Node.js and requires no separate test-runner dependency. Its deterministic dispatcher delegates exact/cumulative layer selection, numbered-fixture traversal, and explicit concern discovery to the gh-workspace-data v0.5.0 runtime. #/public/tests/index.json selects the package's isIdnHostname callback and declares it backwards compatible, so numeric package fixtures accumulate semantically without being copied between version folders. Explicit concern suites retain exact-scope selection, and the matching Unicode conformance concern receives the complete package API from the root dispatcher.

Each applicable IdnaTestV2.txt vector exercises both isIdnHostname and idnHostname; valid conversions must equal the expected nontransitional ToASCII result. Applicability excludes otherwise-valid NV8/XV8 inputs permitted by default UTS #46 but rejected by this package's IDNA2008 policy and valid trailing-root inputs rejected by the package's presentation policy. U1 statuses are ignored because preprocessing uses UseSTD3ASCIIRules=false. CONTEXTO classification remains covered by the version-specific package fixtures rather than by the Unicode concern's applicability logic.

The Unicode fixture registrar verifies that the runtime's Unicode data is at least version 17.0 before registering vectors. The materialized #/public/tests/README.md documents the portable layout, and #/public/tests/v17.0/idna-test-v2/README.md documents source provenance and applicability rules.

npm test exits unsuccessfully when configuration, fixture loading, suite registration, conversion output, or a test fails. Continuous integration is configured to run the suite on the exact minimum Node.js 24.13.1 LTS runtime and the current compatible Node.js 26 release across Ubuntu, Windows, and macOS.

Benchmarks

The materialized benchmark suite provides portable, version-aware measurements for package loading and the three package-owned function exports, including separate ASCII and internationalized inputs for both isIdnHostname and idnHostname.

Benchmark details

Run the standard workload:

npm run benchmark

Run a reduced workload with machine-readable output for CI smoke checks or artifacts:

node ./#/public/benchmarks --quick --json

Direct invocation also supports an explicit iteration count; npm run benchmark retains the standard 100,000 iterations per sample:

node ./#/public/benchmarks --iterations 250000

The harness records five initial calls, warmed minimum/median/p95/maximum latency in milliseconds with six decimal places, integer operations per second, representative arguments, workload counts, and environment metadata. Its generic coordinator delegates exact or cumulative layer selection and ordered concern discovery to the gh-workspace-data v0.5.0 runtime. The materialized #/public/benchmarks/README.md documents concern registration, version eligibility, workload controls, output fields, and guidance for noisy CI runners. Benchmark values are observations rather than correctness assertions.

Versioning

The package version identifies the Unicode version targeted by its bundled data. The major and minor package-version components correspond to the Unicode major and minor version, while the patch component identifies package fixes and revisions that retain the same Unicode target.

Each release ships one Unicode data table and does not select a table at runtime. Runtime compatibility and selection of an appropriate package release are the consumer's responsibility.

When a release changes the targeted Unicode version, its documentation describes compatibility with the preceding release line and identifies any known cases in which input accepted by that preceding release becomes invalid.

The 17.0.x release line targets Unicode 17.0.0 and follows the 16.0.x release line, which targets Unicode 16.0.0. Unicode 17.0 expands the accepted repertoire. Comparison of the complete compact tables found no change to final eligibility, preprocessing behavior, mappings, viramas, bidi classes, or joining types that invalidates an input accepted by the 16.0 release line.

Authoritative references

Disclaimer

The examples exercise mapping and validation rules; they are not registration recommendations. Registries and registrars may impose additional script, language, repertoire, confusability, or policy restrictions.

Keywords