JSON Schema String Formats: email, uri, date-time, uuid, and More
Last updated:
The JSON Schema format keyword annotates string fields with a semantic type. Format validation is opt-in — disabled by default in most validators including Ajv. This reference covers all standard formats, how to enable validation in JavaScript and Python, and how to define custom formats.
1. All Standard Formats Reference
| Format | Standard | Example valid value | Description |
|---|---|---|---|
date-time | RFC 3339 | 2026-05-19T14:30:00Z | Date and time with timezone |
date | RFC 3339 | 2026-05-19 | Calendar date only |
time | RFC 3339 | 14:30:00Z | Time with timezone, no date |
duration | ISO 8601 | P1Y2M3DT4H | Duration (Draft 2019-09+) |
email | RFC 5321 | alice@example.com | Email address |
idn-email | RFC 6531 | 用户@例子.广告 | Internationalized email |
hostname | RFC 1123 | api.example.com | DNS hostname |
idn-hostname | RFC 5890 | 例子.com | Internationalized hostname |
ipv4 | RFC 2673 | 192.168.1.1 | IPv4 dotted-decimal |
ipv6 | RFC 4291 | 2001:db8::1 | IPv6 address |
uri | RFC 3986 | https://example.com | Absolute URI with scheme |
uri-reference | RFC 3986 | /path/to/resource | Absolute or relative URI |
iri | RFC 3987 | https://例子.com/path | Internationalized URI |
iri-reference | RFC 3987 | /path/例子 | Internationalized URI reference |
uuid | RFC 4122 | 550e8400-e29b-41d4-a716-446655440000 | UUID (any version) |
uri-template | RFC 6570 | https://api.example.com/users/{id} | URI template |
json-pointer | RFC 6901 | /foo/0/bar | JSON Pointer path |
relative-json-pointer | draft | 0/foo | Relative JSON Pointer |
regex | ECMA 262 | ^[a-z]+$ | Valid regular expression |
byte | RFC 4648 | SGVsbG8= | Base64-encoded bytes (OpenAPI) |
binary | OpenAPI | (raw bytes) | Binary file upload (OpenAPI only) |
password | OpenAPI | any string | Hint to UI: mask this field (OpenAPI only) |
2. Enabling Format Validation in Ajv
npm install ajv ajv-formatsimport Ajv from 'ajv'
import addFormats from 'ajv-formats'
// Format validation is OFF by default — must be added
const ajv = new Ajv()
addFormats(ajv) // ← enables all standard formats
const schema = {
type: 'object',
properties: {
email: { type: 'string', format: 'email' },
website: { type: 'string', format: 'uri' },
createdAt: { type: 'string', format: 'date-time' },
userId: { type: 'string', format: 'uuid' },
ipAddr: { type: 'string', format: 'ipv4' },
},
required: ['email', 'createdAt'],
}
const validate = ajv.compile(schema)
validate({ email: 'alice@example.com', createdAt: '2026-05-19T10:00:00Z' }) // ✅
validate({ email: 'not-an-email', createdAt: '2026-05-19T10:00:00Z' }) // ❌
validate({ email: 'alice@example.com', createdAt: 'May 19 2026' }) // ❌
// Enable only specific formats
addFormats(ajv, ['email', 'date-time', 'uuid'])3. Format Validation in Python (jsonschema)
from jsonschema import validate, FormatChecker, ValidationError
schema = {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"created_at": {"type": "string", "format": "date-time"},
"website": {"type": "string", "format": "uri"},
}
}
# Without FormatChecker — format is ignored
validate({"email": "not-an-email", "created_at": "bad"}, schema) # passes!
# With FormatChecker — format is validated
validate(
{"email": "alice@example.com", "created_at": "2026-05-19T10:00:00Z"},
schema,
format_checker=FormatChecker()
) # ✅
# With invalid data — raises ValidationError
try:
validate(
{"email": "not-an-email", "created_at": "2026-05-19T10:00:00Z"},
schema,
format_checker=FormatChecker()
)
except ValidationError as e:
print(e.message) # 'not-an-email' is not a 'email'4. Using Formats in Zod
import { z } from 'zod'
// Zod has built-in format validators (no extra config needed)
const schema = z.object({
email: z.string().email(),
website: z.string().url(),
uuid: z.string().uuid(),
createdAt: z.string().datetime(), // ISO 8601 with timezone
date: z.string().date(), // YYYY-MM-DD (Zod 3.22+)
time: z.string().time(), // HH:MM:SS (Zod 3.22+)
ip: z.string().ip(), // ipv4 or ipv6
ipv4: z.string().ip({ version: 'v4' }),
})
// Custom format with regex
const zipCode = z.string().regex(/^d{5}(-d{4})?$/, 'Invalid ZIP code')
// Custom transform + validation
const isoDate = z.string().refine(
(s) => !isNaN(Date.parse(s)),
'Must be a valid date string'
)5. Adding Custom Formats to Ajv
import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = new Ajv()
addFormats(ajv)
// Custom format: US phone number
ajv.addFormat('phone-us', {
type: 'string',
validate: (value: string) => /^+1[0-9]{10}$/.test(value),
})
// Custom format: credit card (Luhn check)
function luhnCheck(num: string): boolean {
const digits = num.replace(/D/g, '')
let sum = 0
let isEven = false
for (let i = digits.length - 1; i >= 0; i--) {
let d = parseInt(digits[i])
if (isEven && (d *= 2) > 9) d -= 9
sum += d
isEven = !isEven
}
return sum % 10 === 0
}
ajv.addFormat('credit-card', {
type: 'string',
validate: (value: string) => /^d{13,19}$/.test(value) && luhnCheck(value),
})
// Use in schema
const paymentSchema = {
type: 'object',
properties: {
cardNumber: { type: 'string', format: 'credit-card' },
phone: { type: 'string', format: 'phone-us' },
},
}6. Date-Time Format Patterns
// ✅ Valid date-time values
"2026-05-19T14:30:00Z" // UTC
"2026-05-19T14:30:00+05:30" // UTC+5:30 (India)
"2026-05-19T14:30:00.123Z" // With milliseconds
"2026-05-19T14:30:00.123456789Z" // With nanoseconds
// ❌ Invalid date-time values
"2026-05-19" // date only — use "date" format
"2026-05-19 14:30:00" // space instead of T
"2026-05-19T14:30:00" // missing timezone
"May 19, 2026" // human-readable format
// ✅ Valid date values
"2026-05-19"
"2026-12-31"
// ✅ Valid uuid values
"550e8400-e29b-41d4-a716-446655440000" // lowercase
"550E8400-E29B-41D4-A716-446655440000" // uppercase (also valid)
// For UUID v4 specifically (pattern constraint)
// { "type": "string", "format": "uuid", "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }Frequently Asked Questions
Does JSON Schema validate format by default?
No — the JSON Schema specification says format is an annotation by default, not a validation constraint. Validators are NOT required to reject strings that fail format checks. In Ajv (the most popular JavaScript validator), format validation is opt-in: you must pass { allErrors: true } and install ajv-formats, then call addFormats(ajv). Without this, a schema with "format": "email" will pass validation for the string "not-an-email" — the format keyword is simply ignored. In Python's jsonschema library, format validation requires FormatChecker: validate(instance, schema, format_checker=FormatChecker()). This opt-in design exists because format validation can be expensive and because format definitions themselves are somewhat ambiguous (e.g., "email" could mean RFC 5321 or RFC 5322, which have different rules). Always explicitly enable format validation if you rely on it for security or data quality.
What is the difference between "date-time", "date", and "time" formats?
"date-time" validates an RFC 3339 date-time string with timezone: "2026-05-19T14:30:00Z" or "2026-05-19T14:30:00+05:30". It requires both date and time components and a timezone offset (Z or ±HH:MM). "date" validates an RFC 3339 full-date: "2026-05-19" — year-month-day only, no time or timezone. "time" validates an RFC 3339 full-time: "14:30:00Z" or "14:30:00.123+05:30" — time and timezone only, no date. "duration" (added in Draft 2019-09) validates ISO 8601 durations: "P1Y2M3DT4H5M6S" (1 year, 2 months, 3 days, 4 hours, 5 minutes, 6 seconds). For most APIs, store dates as "date-time" (full ISO 8601 with UTC offset) for unambiguous timestamps. Use "date" when only the calendar date matters (birthdates, deadlines). Use "time" only when the date is separately tracked or irrelevant.
How do I validate email addresses with JSON Schema?
Use "format": "email" with ajv-formats enabled. In Ajv: const ajv = new Ajv(); addFormats(ajv); const schema = { type: "string", format: "email" }; ajv.validate(schema, "alice@example.com") // true; ajv.validate(schema, "not-an-email") // false. The "email" format in ajv-formats validates against a regex that covers common email patterns. For stricter RFC 5321 validation, "idn-email" covers internationalized email addresses with Unicode local parts. Important limitations: format validation cannot verify that the email domain exists or that the mailbox accepts mail — that requires actually sending a verification email. For user registration, combine format validation (catch obvious non-emails) with email confirmation (verify deliverability). The format validator also does not reject syntactically valid but practically useless emails like "a@b.c" — add a minLength constraint alongside format for better coverage.
What is the "uri" format and when should I use "uri-reference" instead?
"uri" validates an absolute URI per RFC 3986 — it must have a scheme (https://, mailto:, etc.). "2026-05-19T14:30:00Z" fails; "https://example.com/path" passes. "uri-reference" validates both absolute URIs and relative references — "/path/to/resource", "../other", and "?query=1" all pass. Use "uri" when the value must be an absolute URL (avatar URLs, external links, webhook endpoints). Use "uri-reference" when relative paths are also acceptable (internal API hrefs, HTML src attributes). "iri" and "iri-reference" are the internationalized variants that accept Unicode characters in domain names and paths. "uri-template" validates RFC 6570 URI templates: "https://api.example.com/users/{userId}" — useful for API definition schemas. One practical note: "uri" validation does not check that the URL is reachable or that the domain resolves — it only verifies the string conforms to RFC 3986 syntax.
How do I validate UUIDs in JSON Schema?
"format": "uuid" validates RFC 4122 UUID strings in the canonical hyphenated format: "550e8400-e29b-41d4-a716-446655440000". It requires the standard 8-4-4-4-12 hex character pattern. Note: ajv-formats validates UUIDs case-insensitively — both uppercase and lowercase hex digits are accepted. If you need to enforce lowercase specifically (common in APIs), add a pattern constraint: "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$". If your API uses ULID (sortable, URL-safe) or nanoid instead of UUID, "format": "uuid" will not validate those — use a custom format or a pattern constraint for those ID types. For UUID v4 specifically (random), the 15th character must be 4 and the 20th must be 8, 9, a, or b — add a pattern if you need version-specific validation.
How do I add a custom format to JSON Schema validation?
In Ajv, add custom formats with ajv.addFormat(name, validator). The validator can be a regex or a function returning boolean: ajv.addFormat("zip-code", /^\d{5}(-\d{4})?$/); ajv.addFormat("credit-card", { type: "string", validate: (s) => luhnCheck(s) }). Then use "format": "zip-code" in any string schema. For Zod, add custom format validation inline: z.string().regex(/^\d{5}(-\d{4})?$/, "Invalid ZIP code"). For jsonschema (Python): fc = FormatChecker(); @fc.checks("zip-code", raises=ValueError) def check_zip(value): if not re.match(r"^\d{5}(-\d{4})?$", value): raise ValueError; validate(data, schema, format_checker=fc). Custom formats are powerful for domain-specific validation like credit card Luhn checks, ISBN, IBAN, phone number formats, or country-specific ID numbers. Document custom format names in your schema's $schema or description to help consumers understand what the format expects.
What is the "byte" format in JSON Schema?
"byte" validates base64-encoded strings per RFC 4648. It is used in API schemas (especially those generated from Protocol Buffer definitions) for binary data encoded as strings: image content, PDF files, cryptographic keys. The string must contain only base64 characters (A-Z, a-z, 0-9, +, /) and optional = padding. ajv-formats validates the "byte" format. In OpenAPI 3.x, "format": "byte" is the standard way to declare a base64-encoded field in a JSON request/response body — the alternative is "format": "binary" for multipart form uploads, where the actual bytes are sent directly. For large binary payloads, base64 encoding increases size by ~33%, so prefer multipart/form-data (binary) over base64 (byte) for file uploads. Use "byte" for small embedded assets, cryptographic tokens, or when the binary data must be embedded inline in a JSON document.
How do I use JSON Schema formats to validate IP addresses and hostnames?
"ipv4" validates IPv4 dotted-decimal notation: "192.168.1.1". It must have exactly four octets, each between 0 and 255. "192.168.1.256" fails; "192.168.1.1" passes. "ipv6" validates IPv6 addresses in standard notation with colons: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" and compressed forms like "::1" (loopback) or "fe80::1". "hostname" validates DNS hostnames per RFC 1123 — lowercase and uppercase letters, digits, and hyphens, each label 1-63 characters, total length 1-253 characters. Hostnames cannot start or end with a hyphen. "idn-hostname" extends "hostname" to allow Unicode characters in internationalized domain names. Use "ipv4" or "ipv6" for network configuration schemas (firewall rules, IP allowlists). Use "hostname" for server or domain name fields. Use "uri" for full URLs. Note that "hostname" does not validate that the hostname resolves — it only checks syntax.
Further reading and primary sources
- JSON Schema — format keyword — Official JSON Schema docs on the format keyword and all standard format values
- ajv-formats on npm — The Ajv plugin that enables all standard JSON Schema format validators
- Ajv JSON Schema Guide (Jsonic) — Complete Ajv setup: compile, addFormats, errors, TypeScript, and async validation
- Zod Schema Validation (Jsonic) — Zod built-in string validators: email(), url(), uuid(), datetime() — no setup needed
- JSON Schema Draft 2020-12 (Jsonic) — Format as an annotation vocabulary in Draft 2020-12 and the format-assertion vocabulary