-
-
Notifications
You must be signed in to change notification settings - Fork 266
Description
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:
- PR that attempted to add this feature but was closed without a clear explanation: feat: add domain name action #907
- Issue that was auto-closed due to inactivity: improvement : add domain name validation #894
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 whenallowUnderscore
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 anyallowedTLDs
/disallowedTLDs
filters.
Examples
Valid (with defaults):
example.com
sub.example.co.uk
xn--bcher-kva.de
(IDNA form)bücher.de
whenallowUnicode: 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
whenrequireTLD: 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-processinghostname
. 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 atoASCII
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.