Type Formats

String Formats

Emails, hosts, URLs, IPs, card numbers, regex patterns and string transformations.

Named String Formats

Each format also has a builder, named in lowercase (TF.email(), TF.uuidv4()). Date strings are on the Date and Time Formats page.

GroupFormats
TextTF.Alpha, TF.AlphaNumeric, TF.Numeric, TF.Lowercase, TF.Uppercase, TF.Capitalize
EncodingsTF.Base64, TF.Base32, TF.Base16, TF.JsonContent, TF.JsonContentBase64
IdsTF.UUID, TF.UUIDv4, TF.UUIDv7
EmailTF.Email, TF.EmailAddress, TF.IdnEmail, TF.EmailPunycode, TF.EmailParts
Domains and hostsTF.Domain, TF.DomainUnicode, TF.DomainPunycode, TF.DomainParts, TF.Hostname, TF.IdnHostname
IP addressesTF.IP, TF.IPv4, TF.IPv6, TF.IPWithPort, TF.IPv4WithPort, TF.IPv6WithPort
URLsTF.StringUrl, TF.StringUrlHttp, TF.StringUrlFile, TF.Uri, TF.UriReference, TF.UriTemplate, TF.Iri, TF.IriReference
JSON Schema keywordsTF.StringDuration (P4DT12H30M5S), TF.JsonPointer, TF.RelativeJsonPointer, TF.RegexString
PaymentTF.CreditCard

Email Formats

Pick the email format by how much of the address grammar you want to accept.

const isEmail = createValidateFn<TF.Email>();
const isEmailAddress = createValidateFn<TF.EmailAddress>();
const isIdnEmail = createValidateFn<TF.IdnEmail>();

isEmail('[email protected]'); // true
isEmail('"ada lovelace"@example.com'); // false, quoted local part
isEmailAddress('"ada lovelace"@example.com'); // true
isEmailAddress('ada@[127.0.0.1]'); // true, address literal
isIdnEmail('δοκιμή@example.com'); // true, any script
isEmail('ada@localhost'); // false, the domain needs a dot
FormatUse it for
TF.EmailForm fields: the everyday [email protected] shape
TF.EmailAddressAny valid address, quoted local parts and [ip] domains included
TF.IdnEmailThe same as TF.EmailAddress, in any script
All of them need a dotted domain. TF.EmailPunycode takes a domain in its xn-- form. TF.EmailParts limits each half of the address with its localPart and domain options.

Hostnames and Domains

TF.Domain needs a dotted name, TF.Hostname also takes a single label like localhost, and TF.IdnHostname takes names in any script. Set allowedValues to accept only some names. TF.DomainParts limits each label or the number of parts with names, tld, maxParts and minParts.

const isDomain = createValidateFn<TF.Domain>();
const isHostname = createValidateFn<TF.Hostname>();
const isIdnHostname = createValidateFn<TF.IdnHostname>();

isDomain('example.com'); // true
isDomain('localhost'); // false, needs a dot
isHostname('localhost'); // true
isIdnHostname('실례.테스트'); // true

const isOurSite =
  createValidateFn<
    TF.Domain<{allowedValues: {val: ['example.com', 'mion.io']}}>
  >();
const isShortDomain = createValidateFn<TF.DomainParts<{maxParts: 3}>>();

isOurSite('mion.io'); // true
isOurSite('other.org'); // false, not in the list
isShortDomain('aa.bb.example.com'); // false, more than 3 parts

IP Addresses

The IP formats take only an address. Set allowLocalHost to accept localhost too, or use a WithPort format for host:port.

const isIPv4 = createValidateFn<TF.IPv4>();
const isLocalIPv4 = createValidateFn<TF.IPv4<{allowLocalHost: true}>>();
const isHostPort = createValidateFn<TF.IPv4WithPort>();

isIPv4('127.0.0.1'); // true
isIPv4('localhost'); // false
isLocalIPv4('localhost'); // true
isHostPort('10.0.0.1:8080'); // true

URL Objects

TF.Url, TF.UrlHttp and TF.UrlFile check a real URL object instead of a string. They take the maxLength, minLength and pattern options of TF.StringUrl, checked against the href.

interface Bookmark {
  page: URL; // any URL object
  api: TF.UrlHttp<{maxLength: 200}>; // http(s) only, href up to 200 chars
  backup: TF.UrlFile; // file:// only
}

const isBookmark = createValidateFn<Bookmark>();
const decodeBookmark = createJsonDecoderFn<Bookmark>();

const bookmark = decodeBookmark(
  '{"page":"mailto:[email protected]","api":"https://example.com/v1","backup":"file:///tmp/b.json"}'
);
bookmark.api.pathname; // '/v1', a real URL object
isBookmark(bookmark); // true
isBookmark({...bookmark, api: 'https://example.com/v1'}); // false, a string is not a URL
On the wire a URL is its href string. An invalid URL string makes the decoder throw. The builders are TF.url(), TF.urlHttp() and TF.urlFile().

Credit Card Numbers

TF.CreditCard checks the number with the Luhn checksum. Set networks to accept only some card brands.

type Card = TF.CreditCard; // 12 to 19 digits with a valid checksum
type Accepted = TF.CreditCard<{networks: ['visa', 'mastercard']}>;

const isCard = createValidateFn<Card>();
const isAccepted = createValidateFn<Accepted>();

isCard('4111 1111 1111 1111'); // true, spaces and dashes allowed
isCard('4111111111111112'); // false, one digit off
isAccepted('378282246310005'); // false, a valid Amex card
Mocks build a valid number for each network. Set the testCreditCards mock option when the data goes to a payment sandbox.

Regex Patterns

Write a pattern as {source: '...'}, not a regex literal. To reuse one across types, register it with registerFormatPattern.

type Sku = TF.String<{pattern: {source: '^[A-Z]{3}-[0-9]{4}$'}}>;
const isSku = createValidateFn<Sku>();

isSku('ABC-1234'); // true
isSku('abc-1234'); // false

Mock Values for Patterns

Mocks for a pattern come from its mockSamples, or from values the build generates from the regex when you give none.

// no samples: generated from the regex
type Code = TF.String<{pattern: {source: '^[A-Z]{2}[0-9]{3}$'}}>;
// your samples win
type Slug = TF.String<{
  pattern: {source: '^[a-z-]+$'; mockSamples: ['my-post', 'hello-world']};
}>;
Samples must match the pattern and its length bounds, or the build fails. Generated values change on every build unless you pass a literal seed (see Mocking).

Unsafe Regex Patterns

The build rejects a pattern that can freeze the validator on a crafted input, such as a nested repeat. If you checked the pattern yourself, set unsafePattern: true.

// a nested repeat
type Words = TF.String<{
  pattern: {source: '^(\\w+\\s?)*$'; unsafePattern: true};
}>;

String Transformations

A transformation rewrites a string, such as trimming spaces or lowercasing an email. You typically use it to sanitize user input before you store it.

type SignUp = {
  email: TF.Email<{transform: {trim: true; lowercase: true}}>;
  name: TF.String<{maxLength: 32; transform: {trim: true; capitalize: true}}>;
  tags: TF.Transform<string, {lowercase: true}>[]; // the wrapper works on any string
};

const sanitize = createFormatTransformFn<SignUp>();
sanitize({email: ' [email protected] ', name: ' ada ', tags: ['News']});
// {email: '[email protected]', name: 'Ada', tags: ['news']}
TransformationDoes
trimRemoves spaces at both ends
lowercase, uppercase, capitalizeChanges the case
replace, replaceAllReplaces the first match, or every match
stripSeparatorsRemoves spaces and dashes (TF.CreditCard only)
Validation never transforms a value. Only createFormatTransformFn does, and a mion route with sanitizeParams runs it on the params before validation.

Transformation Rules

  • They run in a fixed order: replacements and the separator strip first, then trim, then the case change.
  • TF.Email, TF.Domain, TF.IP and TF.StringUrl never change case unless you ask, because parts of them are case-sensitive.
  • A mion route sanitizes on both ends, so applying a transformation twice must give the same result. Prefer replaceAll over replace, and use one case change per field.
Copyright © 2026