Skip to main content

Recipe

This recipe shows how to validate data structures using Zod, including synchronous and asynchronous validation patterns.
Zod version 3.22+ is supported, which provides the zod/v3 export path required by libraries like json-schema-to-zod.

Code example

Synchronous validation

import { z } from "zod";

// Define the Contract schema
const ContractSchema = z.object({
  contractId: z.string().uuid("Invalid contract ID format"),
  clientName: z.string().min(1, "Client name is required"),
  contractValue: z.number().min(0, "Contract value must be non-negative"),
  status: z.union([
    z.literal("active"),
    z.literal("completed"),
    z.literal("terminated"),
  ]),
  startDate: z.string().datetime("Invalid start date format"),
  endDate: z.string().datetime("Invalid end date format").optional(),
});

// Infer TypeScript type from schema
type Contract = z.infer<typeof ContractSchema>;

// Example data
const validContract = {
  contractId: "550e8400",
  clientName: "Client X",
  contractValue: 50000,
  status: "active" as const,
  startDate: "2024-01-01T00:00:00Z",
  endDate: "2024-12-31T23:59:59Z",
};

const invalidContract = {
  contractId: "invalid-uuid",
  clientName: "",
  contractValue: -1000, // invalid: negative value
  status: "active" as const,
  startDate: "2024-01-01T00:00:00Z",
};

// Synchronous validation with safeParse()

const result1 = ContractSchema.safeParse(validContract);
if (!result1.success) {
  console.error("Validation errors:", result1.error.format());
} else {
  console.log("✓ Valid contract:", result1.data);
}

// Handling validation errors
const result2 = ContractSchema.safeParse(invalidContract);
if (!result2.success) {
  console.error("Validation errors:", result2.error.format());
} else {
  console.log("Valid contract:", result2.data);
}

// Alternative: parse() method (throws on error)
try {
  const validated = ContractSchema.parse(validContract);
  console.log("\n✓ Parsed successfully:", validated);
} catch (error) {
  if (error instanceof z.ZodError) {
    console.error("Validation failed:", error.issues);
  }
}

Asynchronous validation

import { z } from "zod";

const asyncContract = {
  contractId: "660e8400",
  clientName: "Client Y",
  contractValue: 75000,
  status: "active" as const,
  startDate: "2024-06-01T00:00:00Z",
};

// Asynchronous validation with refinements

const AsyncContractSchema = ContractSchema.extend({
  clientName: z.string().refine(
    async (val) => {
      // Simulate database check for duplicate client
      await new Promise((resolve) => setTimeout(resolve, 100));
      return val !== "Client X"; // Fail if client already has active contract
    },
    { message: "Client already has an active contract" }
  ),
});

AsyncContractSchema.safeParseAsync(asyncContract)
  .then((result) => {
    if (!result.success) {
      console.error("✗ Async validation errors:", result.error.format());
    } else {
      console.log("✓ Valid contract (async):", result.data);
    }
  })
  .catch((err) => console.error("Unexpected error:", err));

safeParse() vs parse()

  • safeParse(): Returns a result object with success boolean. Safe for unknown data.
  • parse(): Throws ZodError on validation failure. Use with try-catch blocks.

TypeScript SDK

Learn about the Intuned Browser SDK.