Back to Zod

Basic usage

packages/docs/content/basics.mdx

4.5.46.3 KB
Original Source

import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; import { Callout } from "fumadocs-ui/components/callout";

This page will walk you through the basics of creating schemas, parsing data, and using inferred types. For complete documentation on Zod's schema API, refer to Defining schemas.

Defining a schema

Before you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}> <Tab value="Zod">

ts
import * as z from "zod"; 

const Player = z.object({ 
  username: z.string(),
  xp: z.number()
});
</Tab> <Tab value="Zod Mini"> ```ts import * as z from "zod/mini"

const Player = z.object({ username: z.string(), xp: z.number() });

</Tab>
</Tabs>

## Parsing data

Given any Zod schema, use `.parse` to validate an input. If it's valid, Zod returns a strongly-typed *deep clone* of the input. 

```ts
Player.parse({ username: "billie", xp: 100 }); 
// => returns { username: "billie", xp: 100 }
<Callout> **Note** — If your schema uses certain asynchronous APIs like `async` [refinements](/api#refinements) or [transforms](/api#transforms), you'll need to use the `.parseAsync()` method instead.
ts
await Player.parseAsync({ username: "billie", xp: 100 }); 
</Callout>

Handling errors

When validation fails, the .parse() method will throw a ZodError instance with granular information about the validation issues.

<Tabs groupId="lib" items={["Zod", "Zod Mini"]}> <Tab value="Zod">

ts
try {
  Player.parse({ username: 42, xp: "100" });
} catch(error){
  if(error instanceof z.ZodError){
    error.issues; 
    /* [
      {
        expected: 'string',
        code: 'invalid_type',
        path: [ 'username' ],
        message: 'Invalid input: expected string'
      },
      {
        expected: 'number',
        code: 'invalid_type',
        path: [ 'xp' ],
        message: 'Invalid input: expected number'
      }
    ] */
  }
}
</Tab> <Tab value="Zod Mini"> ```ts try { Player.parse({ username: 42, xp: "100" }); } catch(error){ if(error instanceof z.core.$ZodError){ error.issues; /* [ { expected: 'string', code: 'invalid_type', path: [ 'username' ], message: 'Invalid input: expected string' }, { expected: 'number', code: 'invalid_type', path: [ 'xp' ], message: 'Invalid input: expected number' } ] */ } } ``` </Tab> </Tabs>

To avoid a try/catch block, you can use the .safeParse() method to get back a plain result object containing either the successfully parsed data or a ZodError. The result type is a discriminated union, so you can handle both cases conveniently.

ts
const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
  result.error;   // ZodError instance
} else {
  result.data;    // { username: string; xp: number }
}
<Callout> **Note** — If your schema uses certain asynchronous APIs like `async` [refinements](/api#refinements) or [transforms](/api#transforms), you'll need to use the `.safeParseAsync()` method instead.
ts
await schema.safeParseAsync("hello");
</Callout>

When you only need to know whether an input is acceptable, use the top-level z.validate() function. It returns a boolean, never builds an error, and acts as a type guard on the schema's input type.

ts
z.validate(Player, { username: "billie", xp: 100 }); // true
z.validate(Player, { username: 42, xp: "100" });     // false

Use z.validateAsync() for schemas with async refinements or transforms. On a compiled schema, z.validate() answers directly from the compiled fast path.

Inferring types

Zod infers a static type from your schema definitions. You can extract this type with the z.infer<> utility and use it however you like.

ts
const Player = z.object({ 
  username: z.string(),
  xp: z.number()
});

// extract the inferred type
type Player = z.infer<typeof Player>;

// use it in your code
const player: Player = { username: "billie", xp: 100 };

In some cases, the input & output types of a schema can diverge. For instance, the .transform() API can convert the input from one type to another. In these cases, you can extract the input and output types independently:

ts
const mySchema = z.string().transform((val) => val.length);

type MySchemaIn = z.input<typeof mySchema>;
// => string

type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number

Matching an existing type

Sometimes the type comes first: a database model, a type from a generated client, an interface you don't own. Pass it to z.toZod<T>() and TypeScript checks that the schema's output type is exactly T.

ts
type Player = {
  username: string;
  xp: number;
};

const Player = z.toZod<Player>()(
  z.object({
    username: z.string(),
    xp: z.number(),
  })
);

Player.shape.username; // ZodString — the schema is returned unchanged

The check is exact type equality, so any drift from the type is a compile error:

ts
z.toZod<Player>()(
  z.object({
    username: z.string(),
    xp: z.number(),
    admin: z.boolean(), // ❌ extra key
  })
);

The usual alternative is satisfies z.ZodType<Player>, which only checks assignability. It catches a missing required key, but extra keys, omitted optional keys, and a bare z.any() all slip through:

ts
z.object({
  username: z.string(),
  xp: z.number(),
  admin: z.boolean(),
}) satisfies z.ZodType<Player>; // ✅ no error

z.any() satisfies z.ZodType<Player>; // ✅ no error

Exactness applies to the type as TypeScript wrote it, so an intersection target matches .and() rather than .safeExtend():

ts
type Entry = { id: string } & { label: string };

z.toZod<Entry>()(z.object({ id: z.string() }).and(z.object({ label: z.string() }))); // ✅
z.toZod<Entry>()(z.object({ id: z.string() }).safeExtend({ label: z.string() })); // ❌

Flatten the target and .safeExtend() matches instead:

ts
type Flatten<T> = { [K in keyof T]: T[K] } & {};

z.toZod<Flatten<Entry>>()(z.object({ id: z.string() }).safeExtend({ label: z.string() })); // ✅

Now that we have the basics covered, let's jump into the Schema API.