Skip to content

Proposal: Add domain() validator to Valibot #1277

@yslpn

Description

@yslpn

Proposal: Add domain() validator to Valibot

Summary

Add a built-in domain() validator to Valibot for validating FQDNs and second-level+ domains, with configurable options (Unicode/IDNA, TLD filtering, wildcard, trailing dot, etc.). This covers the common case “validate a domain, not a URL/email/localhost” without having to maintain complex regular expressions.

Prior discussions that were closed:

Please consider adding this validator to core or to an official extensions package.

Problem

Valibot provides email() and url(), but there is no validator specifically for domain names. As a result:

  • Consumers fall back to regex()/pattern(), which is:
    • hard to maintain and reuse,
    • easy to get wrong on edge cases (label length 1–63, overall length, hyphen rules, IDNA/Unicode, TLD constraints, etc.).
  • url() is too broad when only a bare domain (no scheme/path/port) is needed.

Proposed API

Usage examples (in line with existing validators):

import * as v from 'valibot';

const DomainSchema = v.string([
  v.domain(),
]);

// With options
const DomainSchemaStrict = v.string([
  v.domain({
    requireTLD: true,
    allowUnicode: false,
    allowWildcard: false,
    allowTrailingDot: false,
    allowUnderscore: false,
    allowedTLDs: undefined, // e.g. ['com', 'net', /^co\.[a-z]{2}$/]
    disallowedTLDs: undefined,
    maxLength: 253, // overall limit (without trailing dot)
  }),
]);

Suggested options (all optional):

  • requireTLD: boolean (default true) — require a TLD (disallow “intranet”, “localhost”, etc.).
  • allowUnicode: boolean (default false) — support Unicode domains (U-label) by converting to ASCII (A-label) via IDNA/Punycode before rule/length checks.
  • allowWildcard: boolean (default false) — allow a leading *. mask (e.g., *.example.com).
  • allowTrailingDot: boolean (default false) — allow a trailing dot on FQDNs (example.com.).
  • allowUnderscore: boolean (default false) — allow _ in labels (seen in SRV/CNAME/internal domains; not recommended for hostnames by RFCs).
  • allowedTLDs: string[] | RegExp | undefined — allow-list of TLDs (strings without dot, case-insensitive).
  • disallowedTLDs: string[] | RegExp | undefined — deny-list of TLDs.
  • maxLength: number (default 253) — overall domain length limit without trailing dot; each label 1–63.

Returned error: consistent with other validators, with a customizable message.

Validation rules (brief)

  • Composed of labels separated by dots.
  • Each label:
    • length 1–63 characters (in ASCII after IDNA normalization),
    • characters: [a-z0-9-], hyphen not at the start or end,
    • (optional) _ allowed when allowUnderscore is true.
  • Total domain length ≤ 253 characters (without trailing dot) — practical limit (historical limit 255 octets).
  • Unicode: if allowUnicode = true, normalize U-label → A-label (Punycode) before validation; final checks are performed on ASCII form.
  • Wildcard: if allowWildcard = true, exactly one leading *. is permitted before the remaining labels.
  • TLD: if requireTLD = true, the domain must contain at least one dot, and the TLD must pass the label rules and any allowedTLDs/disallowedTLDs filters.

Examples

Valid (with defaults):

  • example.com
  • sub.example.co.uk
  • xn--bcher-kva.de (IDNA form)
  • bücher.de when allowUnicode: true

Invalid:

  • -example.com (hyphen at the start of a label)
  • example-.com (hyphen at the end of a label)
  • ex..ample.com (empty label)
  • example when requireTLD: true
  • example.c (questionable depending on TLD length policy)
  • labelwith63characterslonglabelwith63characterslonglabelwith63.com (label > 63)
  • total domain length > 253

Alternatives/Workarounds

  • Custom regex()/pattern(). Cons: complexity, poor reuse, edge-case coverage.
  • Validating via url() + post-processing hostname. Cons: URL validation is not domain-only; adds overhead and ambiguity.

Backwards compatibility

  • New validator function; does not change existing behavior.
  • Defaults are conservative (ASCII domains, no wildcard, no trailing dot, TLD required).

Performance and size

  • Can be implemented with zero external dependencies (simple label/length checks).
  • For Unicode, possible approaches:
    • minimal built-in IDNA conversion (increases bundle size),
    • or an optional path: when allowUnicode is true, rely on environment capabilities (e.g., new URL('http://' + input).hostname to obtain the A-label) with docs caveats, or expose a toASCII hook in options.

Possible implementation (sketch)

Pseudocode (ASCII branch):

function isAsciiLabel(label: string, allowUnderscore: boolean) {
  const re = allowUnderscore ? /^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/i : /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
  return label.length >= 1 && label.length <= 63 && re.test(label);
}

Request to maintainers

  • Consider adding domain() to Valibot core, or as an official validator in a companion package.
  • Provide feedback on the API/options above.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions