String Formats
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.
| Group | Formats |
|---|---|
| Text | TF.Alpha, TF.AlphaNumeric, TF.Numeric, TF.Lowercase, TF.Uppercase, TF.Capitalize |
| Encodings | TF.Base64, TF.Base32, TF.Base16, TF.JsonContent, TF.JsonContentBase64 |
| Ids | TF.UUID, TF.UUIDv4, TF.UUIDv7 |
TF.Email, TF.EmailAddress, TF.IdnEmail, TF.EmailPunycode, TF.EmailParts | |
| Domains and hosts | TF.Domain, TF.DomainUnicode, TF.DomainPunycode, TF.DomainParts, TF.Hostname, TF.IdnHostname |
| IP addresses | TF.IP, TF.IPv4, TF.IPv6, TF.IPWithPort, TF.IPv4WithPort, TF.IPv6WithPort |
| URLs | TF.StringUrl, TF.StringUrlHttp, TF.StringUrlFile, TF.Uri, TF.UriReference, TF.UriTemplate, TF.Iri, TF.IriReference |
| JSON Schema keywords | TF.StringDuration (P4DT12H30M5S), TF.JsonPointer, TF.RelativeJsonPointer, TF.RegexString |
| Payment | TF.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
| Format | Use it for |
|---|---|
TF.Email | Form fields: the everyday [email protected] shape |
TF.EmailAddress | Any valid address, quoted local parts and [ip] domains included |
TF.IdnEmail | The same as TF.EmailAddress, in any script |
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
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
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']};
}>;
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']}
| Transformation | Does |
|---|---|
trim | Removes spaces at both ends |
lowercase, uppercase, capitalize | Changes the case |
replace, replaceAll | Replaces the first match, or every match |
stripSeparators | Removes spaces and dashes (TF.CreditCard 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.IPandTF.StringUrlnever 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
replaceAlloverreplace, and use one case change per field.