Zod 08 🛡️ String Formats — Top-level z.email(), z.uuid(), z.url() in v4
Zod v4 promotes string-format validators from chained methods to top-level functions. The v3 syntax z.string().email() still parses in v4 but is deprecated and emits warnings in tooling; the canonical v4 form is z.email() . The change is not cosmetic. Top-level functions return dedicated schema types with optimized validation and compose more cleanly than the chained method form . A codebase that mixes the two styles is neither fully v3 nor fully v4, and the deprecation warnings will eventually become removal.
This chapter covers the v4 top-level string formats, the v3-to-v4 migration, and the behavioral changes that come with the promotion. It explains why the functions moved, which formats are available, and how z.uuid() became stricter than its v3 predecessor. The sections that follow walk through the top-level validators, the UUID and email behavioral changes, and the migration path that replaces every chained method with its function equivalent.
Key point: In Zod v4, z.email(), z.uuid(), and z.url() replace z.string().email(), z.string().uuid(), and z.string().url(); the v3 methods are deprecated, and z.uuid() now enforces the RFC 9562/4122 format more strictly than v3 did.
Why top-level string formats exist
The composition problem. A chained method returns a string schema with an added check. A top-level function returns a dedicated format schema that is both the type and the check. The v4 design makes z.email() a schema in its own right, which means it can be used anywhere a schema is accepted without first constructing a string schema and then narrowing it . The difference is small in simple cases and significant in composition: z.array(z.email()) reads as an array of emails; z.array(z.string().email()) reads as an array of strings that happen to be checked for email format.
The deprecation problem. The v3 method forms still work in v4, but they are marked deprecated and slated for removal . A project that upgrades to v4 and continues writing z.string().email() is writing code that will break at the next major release. The migration is mechanical, but it must be done deliberately rather than left for the compiler to flag after removal.
The strictness problem. z.uuid() in v4 enforces the RFC 9562/4122 UUID format more strictly than v3’s z.string().uuid() did. Strings that passed the loose v3 check may fail the v4 function . This is a behavioral change disguised as a syntax change: the migration may surface validation failures in data that was previously accepted, and those failures are correct.
a. The top-level format functions
Zod v4 exposes a family of top-level string-format functions. The ones most commonly used in application code are z.email(), z.uuid(), and z.url(), but the family extends to IP addresses, CIDR blocks, JWTs, and encoding formats .
import { z } from "zod";
// v4: top-level format functions
const EmailSchema = z.email(); // email format
const UrlSchema = z.url(); // URL format
const UuidSchema = z.uuid(); // UUID format (strict RFC)
// The family extends further
const Ipv4Schema = z.ipv4();
const Ipv6Schema = z.ipv6();
const JwtSchema = z.jwt();
const Base64Schema = z.base64();
const EmojiSchema = z.emoji();
Each function returns a schema whose inferred type is string. The schema validates the format on parse; the type remains the primitive string. The difference from z.string() is the runtime check, not the inferred type.
The v3 equivalents are deprecated. They still parse in v4, but the migration guide lists them as removed in the documentation and deprecated in the code .
// v3: deprecated chained methods
const EmailSchemaV3 = z.string().email(); // deprecated
const UrlSchemaV3 = z.string().url(); // deprecated
const UuidSchemaV3 = z.string().uuid(); // deprecated
b. UUID and email behavioral changes
The z.uuid() function is not just a renamed z.string().uuid(). It enforces the RFC 9562/4122 format strictly, which means the version and variant bits are checked. The v3 method used a looser pattern that accepted strings matching the 8-4-4-4-12 hex shape without verifying the version . A UUID generated by a non-standard tool may pass v3 and fail v4.
// Strict v4 UUID: version and variant bits checked
const StrictUuid = z.uuid();
// Version-specific validation
const V4Uuid = z.uuid({ version: "v4" });
const V7Uuid = z.uuidv7(); // convenience function
The email validator also gained options. The default z.email() uses a pattern that approximates browser behavior; z.email({ pattern: z.regexes.rfc5322Email }) uses the full RFC 5322 pattern, and z.email({ pattern: z.regexes.unicodeEmail }) permits internationalized email addresses .
// Default email validation
const BasicEmail = z.email();
// Stricter RFC 5322 validation
const RfcEmail = z.email({ pattern: z.regexes.rfc5322Email });
// Unicode email addresses
const UnicodeEmail = z.email({ pattern: z.regexes.unicodeEmail });
The URL validator similarly gained z.httpUrl() as a convenience for HTTP(S)-only URLs, separate from the general z.url() that accepts any scheme .
c. Migrating from chained methods
The migration is a search-and-replace with a behavioral review attached. Every z.string().<format>() becomes z.<format>(). The formats that moved include email, URL, UUID, CUID, CUID2, ULID, emoji, IPv4, IPv6, CIDR, JWT, and Base64 .
// BEFORE: v3 chained methods (deprecated in v4)
const OldEmail = z.string().email();
const OldUrl = z.string().url();
const OldUuid = z.string().uuid();
const OldIpv4 = z.string().ipv4();
// AFTER: v4 top-level functions
const NewEmail = z.email();
const NewUrl = z.url();
const NewUuid = z.uuid();
const NewIpv4 = z.ipv4();
The migration checklist from the v4 guide is direct: replace z.string().email() with z.email(), replace z.string().uuid() with z.uuid(), and audit the UUID changes because the stricter validation may reject data that previously passed . The z.string().ip() method was split into z.ipv4() and z.ipv6(); it does not have a single top-level equivalent .
The migration should also check for custom regex patterns attached to the old method. z.string().email({ message: "..." }) becomes z.email({ error: "..." }) because v4 unified error customization under the error parameter .
Complete Example Session
// ============================================
// PART 1: THE v3 SYNTAX (DEPRECATED)
// ============================================
import { z } from "zod";
// These still parse in v4 but emit deprecation warnings
const DeprecatedEmail = z.string().email();
const DeprecatedUrl = z.string().url();
const DeprecatedUuid = z.string().uuid();
// ============================================
// PART 2: THE v4 SYNTAX (CANONICAL)
// ============================================
const EmailSchema = z.email();
const UrlSchema = z.url();
const UuidSchema = z.uuid();
// ============================================
// PART 3: BEHAVIORAL CHANGE — STRICTER UUID
// ============================================
// v4 checks version and variant bits
const V4Uuid = z.uuid({ version: "v4" });
const V7Uuid = z.uuidv7();
// A UUID that passed loose v3 may fail strict v4
// if the version bits are not RFC-compliant
// ============================================
// PART 4: EMAIL PATTERN OPTIONS
// ============================================
const BrowserEmail = z.email(); // default approximation
const RfcEmail = z.email({ pattern: z.regexes.rfc5322Email });
const IntlEmail = z.email({ pattern: z.regexes.unicodeEmail });
// ============================================
// PART 5: THE FORMAT FAMILY
// ============================================
const Ipv4Schema = z.ipv4();
const Ipv6Schema = z.ipv6();
const JwtSchema = z.jwt();
const Base64Schema = z.base64();
const EmojiSchema = z.emoji();
// ============================================
// PART 6: MIGRATION SEARCH PATTERN
// ============================================
// Search codebase for: z.string().email()
// Replace with: z.email()
//
// Search codebase for: z.string().uuid()
// Replace with: z.uuid()
// Then audit for stricter validation failures
The parts covered the deprecated v3 syntax, the canonical v4 syntax, the stricter UUID behavior, the email pattern options, the format family, and the migration search pattern.
Quick Reference
Top-Level String Formats (v4)
| Function | Replaces | Checks |
|---|---|---|
z.email() | z.string().email() | Email format |
z.url() | z.string().url() | URL format |
z.uuid() | z.string().uuid() | UUID format (strict) |
z.ipv4() | z.string().ipv4() | IPv4 address |
z.ipv6() | z.string().ipv6() | IPv6 address |
z.jwt() | — | JWT format |
z.base64() | — | Base64 string |
Behavioral Changes
| Change | v3 Behavior | v4 Behavior |
|---|---|---|
| UUID strictness | Loose 8-4-4-4-12 pattern | RFC 9562/4122 version/variant |
| IP split | z.string().ip() | z.ipv4() / z.ipv6() |
| Error parameter | message | error (unified) |
Migration Checklist
| Step | Action |
|---|---|
| 1 | Replace z.string().email() with z.email() |
| 2 | Replace z.string().uuid() with z.uuid() |
| 3 | Audit UUID validation failures (stricter) |
| 4 | Replace z.string().ip() with z.ipv4() or z.ipv6() |
| 5 | Replace message with error in options |
Best Practices
✅ Do This:
// Use top-level functions in new code
z.email() // ✅
// Use the strict UUID function
z.uuid() // ✅
// Specify email pattern when needed
z.email({ pattern: z.regexes.rfc5322Email }) // ✅
// Split IP validation by version
z.ipv4() // ✅
❌ Don’t Do This:
// v3 chained method (deprecated)
z.string().email() // ❌
// Loose UUID expectation
// (v4 is stricter; test your data) // ❌
// Single IP validator
z.string().ip() // removed in v4 // ❌
// Old message parameter
z.email({ message: "Invalid" }) // use error // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Deprecation warnings | v3 chained methods still used | Replace with top-level functions |
| UUID validation fails | v4 enforces RFC version bits | Regenerate or accept stricter check |
| IP validation missing | z.string().ip() removed | Use z.ipv4() or z.ipv6() |
| Error option ignored | Using message instead of error | Change to error parameter |
Real-World Examples
1. Email validation
z.email();
2. URL validation
z.url();
3. Strict UUID
z.uuid();
4. Version-specific UUID
z.uuid({ version: "v4" });
5. Convenience UUID v7
z.uuidv7();
6. RFC email pattern
z.email({ pattern: z.regexes.rfc5322Email });
7. IPv4 validation
z.ipv4();
8. JWT validation
z.jwt();
9. Base64 validation
z.base64();
10. Migration replacement
// z.string().email() → z.email()
Visual
v3 vs v4 Syntax
┌─────────────────────────────────────────────────────────┐
│ v3 (DEPRECATED) v4 (CANONICAL) │
│ │
│ z.string().email() z.email() │
│ z.string().url() z.url() │
│ z.string().uuid() z.uuid() │
│ z.string().ipv4() z.ipv4() │
│ z.string().ipv6() z.ipv6() │
│ │
│ Chained method Top-level function │
│ String schema + check Dedicated format schema │
└─────────────────────────────────────────────────────────┘
UUID Strictness
┌─────────────────────────────────────────────────────────┐
│ v3 z.string().uuid() │
│ ┌──────────────────────────────────────────┐ │
│ │ Loose 8-4-4-4-12 hex pattern │ │
│ │ Version bits not checked │ │
│ └──────────────────────────────────────────┘ │
│ │
│ v4 z.uuid() │
│ ┌──────────────────────────────────────────┐ │
│ │ RFC 9562/4122 format │ │
│ │ Version and variant bits checked │ │
│ │ Non-compliant UUIDs rejected │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Migration Path
┌─────────────────────────────────────────────────────────┐
│ SEARCH AND REPLACE │
│ │
│ z.string().email() ──► z.email() │
│ z.string().url() ──► z.url() │
│ z.string().uuid() ──► z.uuid() │
│ z.string().ip() ──► z.ipv4() / z.ipv6() │
│ │
│ Then audit: │
│ - UUID validation failures (stricter) │
│ - message → error parameter │
└─────────────────────────────────────────────────────────┘
Format Function Family
┌─────────────────────────────────────────────────────────┐
│ z.email() z.url() z.uuid() │
│ z.ipv4() z.ipv6() z.jwt() │
│ z.base64() z.emoji() z.cuid() │
│ z.cuid2() z.ulid() z.cidrv4() │
│ │
│ All return string schemas │
│ All validate format at parse time │
│ All are tree-shakable top-level exports │
└─────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Canonical v4 email | z.email() |
| Canonical v4 URL | z.url() |
| Canonical v4 UUID | z.uuid() |
| Deprecated v3 syntax | z.string().<format>() |
| UUID strictness | RFC 9562/4122, version/variant checked |
| IP split | z.ipv4() / z.ipv6() |
| Error parameter | error replaces message |
| Tree-shakable | Top-level functions are individually importable |
Key takeaways:
- The format validators moved from methods to functions.
z.email()replacesz.string().email(); the v3 method is deprecated and slated for removal . - The top-level functions return dedicated schema types. They are not just string schemas with a check; they are format schemas that compose as first-class values .
z.uuid()is stricter than v3. It validates the RFC 9562/4122 version and variant bits, and data that passed the loose v3 pattern may fail .- The migration is mechanical but requires a behavioral audit. Replace the method calls, then test the stricter UUID validation against real data.
- The error parameter changed.
z.email({ message: "..." })becomesz.email({ error: "..." })under v4’s unified error customization .
Remember: In Zod v4, string formats are functions, not methods. z.email(), z.url(), and z.uuid() are the canonical forms; the chained versions still parse but are deprecated. The migration is a search-and-replace with one substantive change attached: z.uuid() now enforces the RFC format strictly, which means the migration may surface validation failures that v3 never reported. Run the migration, run the tests, and treat any new UUID failures as the stricter validator doing its job.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!