Zod Object Schema: Error Handling, Refine & Nested


Ensuring your objects follow the correct structure is essential for building reliable forms, APIs, and applications. Zod makes this process seamless by letting you define schemas, enforce validation rules, and manage errors directly in TypeScript or JavaScript.

Want to master object validation and eliminate unexpected runtime issues in your projects? Read on to explore practical Zod object schemas and common validation scenarios.

Defining an Object Schema with Zod

When working with forms or API inputs, you often need to ensure the data follows a specific structure. For example, using Zod, you can define a schema for a user object that expects a name and an age.

import { z } from "zod";

const userSchema = z.object({
  name: z.string(),
  age: z.number(),
});

// Example usage
const user = userSchema.parse({ name: "Alice", age: 25 });

Explanation:

  • z.object({...}) → creates a schema for an object.
  • Each key inside the object corresponds to a property you expect in your data.
  • name: z.string() → the name field must be a string.
  • age: z.number() → the age field must be a number (you can also enforce int() or positive() if needed).
  • This defines the validation rules, but it doesn’t automatically check data until you call parse() or safeParse().

Validating an Object Against the Zod Schema

import { z } from "zod";

const userSchema = z.object({
  name: z.string(),
  age: z.number().int().positive(),
});

// Example data to validate
const userData = {
  name: "Sharukhan",
  age: 25,
};

// Validate the data
try {
  const validData = userSchema.parse(userData);
  console.log("Validation Passed:", validData);
} catch (error) {
  if (error instanceof z.ZodError) {
    console.log("Validation Error:", error.errors);
  }
}

Explanation:

  • parse(userData) → checks the object against the schema.
  • If the data is valid, it returns the validated object (validData).
  • If the data is invalid, it throws a ZodError.
  • error.errors contains an array of validation issues, similar to Yup’s .catch(error => error.errors).

Different Validation Scenarios for Zod Object

The z.object method in Zod is powerful because it allows you to define object schemas, apply constraints, handle optional or nullable values, validate nested objects, and even work with dynamic keys. Below, we’ll walk through the most common use cases of z.object, grouped by related scenarios.

Zod Schema for Required Object

const userSchema = z.object({
  name: z.string(),
  age: z.number(),
});

Explanation:

  • This schema ensures that all required fields exist.
  • Use this for Zod object required validation when certain properties must be present.
  • Missing fields throw a ZodError, making it suitable for form submissions and API requests.

Zod Handling Nullable, Undefined, or Optional Object

const addressSchema = z
  .object({
    city: z.string(),
    zip: z.string(),
  })
  .nullable()
  .optional();

// Ensuring object is not empty
const notEmpty = z
  .object({
    city: z.string().optional(),
    zip: z.string().optional(),
  })
  .refine((obj) => Object.keys(obj).length > 0, {
    message: "Object cannot be empty",
  });

Explanation:

  • .nullable() allows the object to be null.
  • .optional() allows the object to be missing entirely.
  • .refine() with Object.keys(obj).length > 0 ensures the object is not empty.
  • This is useful for optional sections of forms or configurations where the object may or may not be provided.

Zod Default Values for Object Properties

const settingsSchema = z.object({
  theme: z.string().default("light"),
});

Explanation:

  • Default values ensure that an object property has a fallback when missing.
  • This handles the Zod object default value scenario and maintains consistency in your application.

Zod Nested Object Validation

const profileSchema = z.object({
  name: z.string(),
  address: z.object({
    city: z.string(),
    zip: z.string(),
  }),
});

Explanation:

  • The outer object has a name property (required string).
  • The address property is itself an object with city and zip, both required strings.
  • This ensures that both the main object and its nested object meet the defined rules.

Zod Object Validation Using .refine() for Custom Rules

Zod provides the .refine() method to perform custom validations on objects, allowing you to enforce rules that depend on multiple fields or complex logic. This is particularly useful for scenarios like confirm password checks, where one field’s value depends on another.

const passwordSchema = z.object({
  password: z.string(),
  confirmPassword: z.string(),
}).refine(data => data.password === data.confirmPassword, {
  message: "Passwords do not match",
  path: ["confirmPassword"],
});

Explanation:

  • .refine() applies a custom validation rule to the entire object.
  • data.password === data.confirmPassword ensures both fields match exactly.
  • message specifies the error shown when the rule fails.
  • path: ["confirmPassword"] directs the error to the correct field in forms.
  • This scenario demonstrates a practical Zod confirm password check, ideal for user registration forms or API input validation.

Zod Object Error Handling

Proper error handling is essential when validating objects with Zod. Without capturing errors, you won’t know whether the object matches the expected structure. Zod throws a ZodError when validation fails, and you can access all validation issues through the errors array.

try {
  const validData = userSchema.parse(userData);
  console.log("Valid Object:", validData);
} catch (err) {
  if (err instanceof z.ZodError) {
    console.log("Object Errors:", err.errors);
  }
}

Explanation:

  • parse() checks the object and throws a ZodError if validation fails.
  • err.errors contains all error messages from the object validation process.
  • For cases where you want to avoid throwing, safeParse() can be used to return a result object with success/failure information.

The examples above demonstrate common patterns for objects, but to see how Zod validates all types of data, including objects, arrays, and primitives, refer to Zod schema validation.

Conclusion

The z.object method is the foundation of object validation in Zod. From handling required fields to optional or nullable data, from nested objects to default values, Zod object schemas are versatile enough to cover nearly all real-world validation needs.

Whether you are working with forms, APIs, or complex data structures, mastering Zod object validation ensures consistent data, fewer bugs, and a more robust application

Author

Sharukhan is the founder of Tecktol. He has worked as a software engineer specializing in full-stack web development.