1. Guides
  2. Zod Guide


Zod is a fantastic utility package by @colinhacks that allows for defining runtime schema validation and type-safety.

We use it extensively internally at Trigger.dev to ensure we never trigger your workflows with invalid data (and more).

But there are a few places where we ask you to provide us with a Zod schema, for example when defining a Custom Event Trigger:

import { Trigger, customEvent } from "@trigger.dev/sdk";
import * as slack from "@trigger.dev/slack";
import { z } from "zod";

//this workflow will run when a "user.created" event is sent
new Trigger({
  id: "zod",
  name: "Zod custom event",
  //this is the custom event subscription
  on: customEvent({
    name: "user.created",
    schema: z.object({
      name: z.string(),
      email: z.string(),
      paidPlan: z.boolean(),
  //this function is run when the custom event is received
  run: async (event, ctx) => {
    console.log(event); // Has name, email, paidPlan

So it will help to know a little about Zod and how to use it. We definitely recommend the well written Zod README but we’ve included a short primer below.

Wherever we require you to pass in a Zod schema, you can always start with z.any() which accepts any type and then add more strict validations later.

Basic Usage

There are three main steps to using Zod:

  1. Define a schema
import { z } from "zod";

const mySchema = z.object({
  name: z.string(),
  email: z.string(),
  paidPlan: z.boolean(),
  1. Infer TypeScript types from the schema
type MySchema = z.infer<typeof mySchema>;
  1. Validate data against the schema
const data: unknown = {
  name: "Eric",
  email: "eric@trigger.dev",
  paidPlan: true,

const result = mySchema.parse(data);

When using Zod with Trigger.dev, you’ll only really need to do the first step, and by passing it to us (through the schema property of the customEvent function), we’ll do the second and third steps for you.

Defining Schemas


Zod schemas are a way to define the shape of an object. They can be as simple as a single type, or as complex as a nested object.

// Primitives

Any schema can be marked as optional, which means the schema can be undefined or null:


Schemas can also be marked optional by providing a default value:

const optionalString = z.string().default("default value");

const value = optionalString.parse(undefined); // value === "default value"

If you need to allow a value to be null, you can use nullable():

const nullableString = z.string().nullable();

const value = nullableString.parse(null);

You can also use Zod to coerce primites into other types. For example, you can coerce a string into a number:

const numberString = z.coerce.number();

const value = numberString.parse("123"); // value === 123

The following primitives are supported:


Coercing dates are especially useful when you are receiving a string from an API and want to convert it to a JavaScript Date object:

const date = z.coerce.date();

const value = date.parse("2021-01-01T00:00:00.000Z"); // value === Date object


Object schemas are the most common type of schema. They allow you to define the shape of an object, and the types of each property.

const mySchema = z.object({
  name: z.string(),
  email: z.string(),
  paidPlan: z.boolean(),

All properties are required by default, although you can make them all optional using partial():

const mySchema = z
    name: z.string(),
    email: z.string(),
    paidPlan: z.boolean(),

You can also make individual properties optional:

const mySchema = z.object({
  name: z.string(),
  email: z.string(),
  paidPlan: z.boolean().optional(),

By default an object schema will strip out any extra properties that are not defined in the schema. You can disable this behavior using passthrough():

const mySchema = z.object({
  name: z.string(),

mySchema.parse({ name: "Eric", email: "eric@trigger.dev" }); // { name: "Eric" }
mySchema.passthrough().parse({ name: "Eric", email: "eric@trigger.dev" }); // { name: "Eric", email: "eric@trigger.dev" }

Or you can use strict() to make the schema throw an error if there are any extra properties:

const mySchema = z
    name: z.string(),

mySchema.parse({ name: "Eric", email: "eric@trigger.dev" }); // throws error

Zod includes a few useful object schema utilities to help with reusing schemas, extends() and merge():

const baseSchema = z.object({
  name: z.string(),

const extendedSchema = baseSchema.extend({
  email: z.string(),

const mergedSchema = baseSchema.merge(
    email: z.string(),

You can also use pick() and omit() to create a new schema that only includes or excludes certain properties:

const mySchema = z.object({
  name: z.string(),
  email: z.string(),
  paidPlan: z.boolean(),

const pickedSchema = mySchema.pick({ name: true, email: true });
const omittedSchema = mySchema.omit({ paidPlan: true });


You can specify the schema of an array using z.array():

z.array(z.string()); // string[]
z.array(z.number()); // number[]
z.array(z.object({ name: z.string() })); // Array<{ name: string }>

You can also specify the type of array that has a fixed number of elements using z.tuple():

z.tuple([z.string(), z.number(), z.boolean()]); // [string, number, boolean]


You can specify a union of schemas using z.union():

z.union([z.string(), z.number(), z.boolean()]); // string | number | boolean

Discriminating unions are also supported, and especially useful when paired with type narrowing:

const mySchema = z.discriminatingUnion("type", [
    type: z.literal("a"),
    data: z.string(),
    type: z.literal("b"),
    data: z.number(),

const value = mySchema.parse({ type: "a", data: "hello" });

if (type.a) {
  // value is { type: "a", data: string }
} else if (type.b) {
  // value is { type: "b", data: number }


You can specify a record of schemas using z.record(), useful for when you have a map of values but don’t care about the keys:

z.record(z.string()); // Record<string, string>
z.record(z.number()); // Record<string, number>

JSON type

If you want to accept any valid JSON value, you can use the following schema:

const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
type Literal = z.infer<typeof literalSchema>;
type Json = Literal | { [key: string]: Json } | Json[];
const jsonSchema: z.ZodType<Json> = z.lazy(() =>
  z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])


Hat tip to ggoodman for this one.


Matt Pocock @mattpocock has a great free Zod Tutorial up on Total Typescript that covers the basics of Zod.

Easily generate Zod schemas from JSON, JSON Schemas, or TypeScript types.