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

FormatStandardExample valid valueDescription
date-timeRFC 33392026-05-19T14:30:00ZDate and time with timezone
dateRFC 33392026-05-19Calendar date only
timeRFC 333914:30:00ZTime with timezone, no date
durationISO 8601P1Y2M3DT4HDuration (Draft 2019-09+)
emailRFC 5321alice@example.comEmail address
idn-emailRFC 6531用户@例子.广告Internationalized email
hostnameRFC 1123api.example.comDNS hostname
idn-hostnameRFC 5890例子.comInternationalized hostname
ipv4RFC 2673192.168.1.1IPv4 dotted-decimal
ipv6RFC 42912001:db8::1IPv6 address
uriRFC 3986https://example.comAbsolute URI with scheme
uri-referenceRFC 3986/path/to/resourceAbsolute or relative URI
iriRFC 3987https://例子.com/pathInternationalized URI
iri-referenceRFC 3987/path/例子Internationalized URI reference
uuidRFC 4122550e8400-e29b-41d4-a716-446655440000UUID (any version)
uri-templateRFC 6570https://api.example.com/users/{id}URI template
json-pointerRFC 6901/foo/0/barJSON Pointer path
relative-json-pointerdraft0/fooRelative JSON Pointer
regexECMA 262^[a-z]+$Valid regular expression
byteRFC 4648SGVsbG8=Base64-encoded bytes (OpenAPI)
binaryOpenAPI(raw bytes)Binary file upload (OpenAPI only)
passwordOpenAPIany stringHint to UI: mask this field (OpenAPI only)

2. Enabling Format Validation in Ajv

npm install ajv ajv-formats
import 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