Published on

Why does Zod make all my schema fields optional?

Authors
  • Name
    Ripal & Zalak
    Twitter

Why does Zod make all my schema fields optional?

Zod is a versatile library for schema validation in TypeScript. However, developers often encounter an issue where Zod seems to make schema fields optional by default. Let’s explore why this happens and how you can ensure your fields are required.

Common Issue: Optional Fields

When validating user input, you may find that Zod treats fields as optional, leading to type conflicts. Consider the following schema:

import { z } from 'zod'

export const createUserSchema = z.object({
  firstName: z.string({
    required_error: 'First name is required',
  }),
  lastName: z
    .string({
      required_error: 'Last name is required',
    })
    .nonempty(),
  password: z
    .string({
      required_error: 'Password is required',
    })
    .min(6, 'Password too short - should be 6 chars minimum'),
  email: z
    .string({
      required_error: 'Email is required',
    })
    .email('Not a valid email')
    .nonempty(),
})

When using the schema to validate input, you might encounter the following error:

Argument of type '{ firstName?: string; lastName?: string; email?: string; }' is not assignable to parameter of type 'CreateUserInput'.

Root Cause

This issue often stems from TypeScript compiler settings, specifically the strict or strictNullChecks options.

Solutions

1. Enable strict or strictNullChecks

Ensure that your TypeScript configuration enforces strict null checks by adding or updating your tsconfig.json:

{
  "compilerOptions": {
    "strict": true
  }
}

Alternatively, if you need a less intrusive option, enable only strictNullChecks:

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

2. Use required_error and min(1)

Zod provides the required_error option to enforce required fields:

const userSchema = z.object({
  firstName: z.string({
    required_error: 'First name is required',
  }),
  lastName: z.string({
    required_error: 'Last name is required',
  }),
  email: z
    .string({
      required_error: 'Email is required',
    })
    .email('Not a valid email'),
})

For non-empty string validation, use .min(1) instead of the deprecated .nonempty():

const userSchema = z.object({
  firstName: z
    .string({
      required_error: 'First name is required',
    })
    .min(1, 'First name cannot be empty'),
})

3. Combine Schemas for Refinements

If you need advanced validation, such as matching passwords, extend an existing schema:

const baseUserSchema = z.object({
  firstName: z.string({
    required_error: 'First name is required',
  }),
  password: z
    .string({
      required_error: 'Password is required',
    })
    .min(6, 'Password too short - should be 6 chars minimum'),
})

const extendedUserSchema = baseUserSchema
  .extend({
    passwordConfirmation: z.string({
      required_error: 'Password confirmation is required',
    }),
  })
  .refine((data) => data.password === data.passwordConfirmation, {
    message: 'Passwords do not match',
    path: ['passwordConfirmation'],
  })

FAQ

Why does Zod treat fields as optional?

This usually occurs when strictNullChecks is disabled in your TypeScript configuration. Zod relies on strict type checking to enforce required fields.

What replaced .nonempty() in Zod?

Use .min(1) for non-empty string validation. For example: z.string().min(1, 'Field cannot be empty').

Can I use Zod without enabling strict mode?

Yes, but you must enable at least strictNullChecks for reliable behavior.

How do I validate dependent fields in Zod?

Use the .refine() method to enforce rules involving multiple fields, such as password matching.