logo
Basic Utils
Home

Zod Enum Tutorial With Examples

Table of Contents

  1. Introduction
  2. Understanding Different Zod Enums
  3. Zod Enum Validation
  4. Creating Zod Enums from Different Data Sources
  5. Handling Zod Enum Error Messages
  6. FAQs(Frequently Asked Questions about Zod Enums)
  7. Sources
Zod Logo
Zod Logo
Zod.dev


Introduction

In systems, there is the concept of constrained values. Some apps have fields designed to accept a specific set of predefined values. Examples of these include roles, statuses, and categories. Make sure to enter only these values to avoid system crashes and issues.

Zod is a TypeScript-first schema validation library. Its key features include simplicity, friendliness, error messages, and static type inference. Zod makes working with these constraints easier. It's used to define and validate schemas for use in data inputs, API responses, or forms. It simplifies coding by providing a well-tested set of validation utilities. The developer can focus on writing business logic.

In this article, we will venture into different types of Zod enums, such as Zod native enums, and Zod string enums. We will also look at validating enums with Zod, creating Zod enums from TypeScript enums, object keys, arrays, and much more.

Understanding Different Zod Enums

Zod Enum vs Zod Native Enum

Zod provides 2 basic ways to handle enums: z.enum() and z.nativeEnum(). Each has its use cases. z.enum()works with string-based enums while z.nativeEnum() works with Typescript native enums.

The basic z.enum() allows you to create a Zod schema that accepts a specific set of values. These values are often strings or numbers. Let us understand this through an example.

import { z } from "zod";
const colorEnum = z.enum(["red", "green", "blue"]);

Once defined this way, colorEnum can only accept the values "red", "green", or "blue". Attempts to pass in a value outside of this result in a validation error.

Zod.nativeEnum allows you to create a zod-enum schemas from typescripts native enum. It's handy especially if you are adding Zod to an existing project. In such scenarios, you might already have enums defined in typescript.

enum UserRole {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST",
}
const userRoleSchema = z.nativeEnum(UserRole);
here is another example
enum Status {
Active = "active",
Inactive = "inactive",
Suspended = "suspended",
} 
const statusSchema = z.nativeEnum(Status);

Zod String Enum

The Zod string enum presents a way to create an enum schema whose values are strictly strings. This is just another name for z.enum().


const categorySchema = z.enum(["Books", "Electronics", "Clothing"]);

Zod Enum Validation

Using Zod enums makes it easy to validate whether a value belongs to an enumerated set. You normally make use of .safeParse() or .parse() to validate values. Let's delve into both of them.

safeParse

As its name suggests, safeParse() offers a safer alternative. safeParse () returns an object that contains validation information. It safely avoids exceptions at the cost of making your code more verbose. It's ideal when handling user input or external data sources. You cannot guarantee validity for such sources.

Here is some code:

const roleResult = RoleSchema.safeParse("guest");

if (!roleResult.success) {
console.log("Validation Error:", roleResult.error.errors);
} else {
console.log("Valid Role:", roleResult.data);
}

[Zod Validate Enum example]

Expected output:

Validation Error: [ { message: 'Invalid enum value...', path: [], ... }]

parse

You would use parse when you are confident that the input is valid. This is because parse () will throw an error upon invalid input. It offers simplicity and directness but brings about extra work in dealing with the exceptions.

Here is the code:

import { z } from "zod";

const RoleSchema = z.enum(["admin", "editor", "viewer"]);
try {
const role = RoleSchema.parse("admin");
console.log("Valid role:", role); // Output: "Valid role: admin"
// This will throw an error
const invalidRole = RoleSchema.parse("guest");
} catch (error) {
console.error("Error:", error.errors[0].message);
}

[Zod Validate Enum example]

Here is the expected output;

Error: Invalid enum value. Expected 'admin' | 'editor' | 'viewer', received 'guest'

Creating Zod Enums from Different Data Sources

As we have seen, Zod is flexible and supports multiple ways to create the enums. Here, we'll cover creating Zod enums from diverse data types.

Creating Zod Enums from Object Keys

In Typescript, we use Objects to define mappings and also configurations. The objects are made up of key-value pairs which are potential enum candidates. This way, you get schemas that reflect your configuration and adapt to changes. Use when schemas need to strictly align with object definitions in your code. This is good for reducing redundancy and improving maintainability.

const permissions = {
READ: "read",
WRITE: "write",
DELETE: "delete",
};
const permissionsEnum = z.enum(Object.keys(permissions));

Working with Arrays of Zod Enums

Zod also allows creating a zod array schema from the zod enum schema. Suppose you have an existing zod enum that defines a color schema. To create a zod array schema that validates an array to contain only the values in the enum, this is the right method. You can define arrays of enums in Zod to ensure each item in the array meets the enum’s constraints.

const colorEnum = z.enum(["red", "green", "blue"]);
const colorArraySchema = z.array(colorEnum);
const colors = colorArraySchema.safeParse(["red", "green", "blue"]);

Now, this validates that Every item in that array has to be one of the values in colorEnum (only "red," "green," or "blue").

Creating Zod Enums from Arrays

Zod allows you to create a zod enum schema from an array. Arrays are a common data structure and having such a utility saves time and removes redundancy. This is a useful approach especially when working with dropdowns, options, tag lists, or any set of values that may evolve.

const dynamicValues = ["small", "medium", "large"] as const;
const sizeEnum = z.enum(dynamicValues);

Creating Zod Enums from Union Types

Unions in Typescript define a variable that can hold one of several specified values. Zod also allows you to create enums from union types. This is useful when you want to enforce a specific set of strings but prefer using Typescripts unions for clearer intent and type inference.

import { z } from "zod";

// Define a union type of possible status values
type Status = "active" | "inactive" | "pending";

// Create a Zod enum schema using the union type
const statusEnum = z.enum(["active", "inactive", "pending"]);

// Example usage: validating a valid status
const result1 = statusEnum.safeParse("active");
console.log(result1.success); // ✅ true

// Example usage: validating an invalid status
const result2 = statusEnum.safeParse("archived");
console.log(result2.success); // ❌ false

Converting Zod Enums to Arrays

We can also reverse the process and extract the values of an enum as an array. To do this we make use of the .options property.

import { z } from "zod";

// Define a Zod enum
const sizeEnum = z.enum(["small", "medium", "large"]);

// Convert the enum to an array using the .options property
const sizes = sizeEnum.options;
console.log(sizes); // ["small", "medium", "large"]

Dynamic enums can be created at runtime, offering flexibility with generated values. Consider the following example.

const dynamicValues = ["A", "B", "C"];
const dynamicEnum = z.enum(dynamicValues);

Working with Zod Enum Numbers

In addition to string-based enums, Zod also supports number-based enums. TO handle this we make use of z.nativeEnum() discussed above. Let us understand this through an example.

import { z } from "zod";

// Define a numeric enum in TypeScript
enum ResponseCode {
Success = 200,
NotFound = 404,
}

// Create a Zod schema for the numeric enum
const responseCodeSchema = z.nativeEnum(ResponseCode);

// Test validation with valid value
const validResponse = responseCodeSchema.safeParse(200);
console.log(validResponse.success); // ✅ true

// Test validation with invalid value
const invalidResponse = responseCodeSchema.safeParse(500);
console.log(invalidResponse.success); // ❌ false
console.log(invalidResponse.error.format()); // Error message for invalid value

Handling Zod Enum Error Messages

When dealing with validation, providing meaningful validation error messages is important. The messages should be meaningful, user-friendly, and intuitive. Zod offers generic error messages which should be enough for common cases. However, for a better user experience, Zod allows custom error messages. We use .refine() to customize errors more effectively. The .refine() combines validation and customization of the error messages. Here is an example

import { z } from "zod";

// Define a simple enum for colors
const colorEnum = z.enum(["red", "green", "blue"]);

// Refine the enum to provide a custom error message
const colorEnumWithMessage = colorEnum.refine((val) => ["red", "green", "blue"].includes(val), {
message: "Color must be red, green, or blue.",
});

// Test validation
const validColor = colorEnumWithMessage.safeParse("red");
console.log(validColor.success); // ✅ true

const invalidColor = colorEnumWithMessage.safeParse("yellow");
console.log(invalidColor.success); // ❌ false
console.log(invalidColor.error.format()); // { message: "Color must be red, green, or blue." }

FAQs(Frequently Asked Questions about Zod Enums)

How to handle enum error messages in Zod?

Zod's default validation error messages should be enough for most common cases. However, sometimes you want your users to understand what's wrong more clearly. For such cases, Zod offers the refine() method. .refine() is used to provide easier to understand validation messages .

Zod Enum vs Union

z.enum() is used when you want to define a schema with a fixed set of known values (e.g., "open", "closed", "pending"). In contrast, z.union() creates a schema that accepts multiple distinct types.

z.union is more flexible. it allows you to combine different types e.g. strings and numbers. A simple rule of thumb would be to take z.enum for a set of strings and z.union when working with mixed types.

Can Zod enums be used with optional fields?

Zod offers the optional () method to mark fields that don't have to be filled at all times. Its useful to avoid validation errors when fields lack values.

How to add optional descriptions to enums in Zod for enhanced schema clarity?

Currently, you can’t directly attach descriptions to Zod enums. However, you can add an optional text field next to your enum field to store any descriptive information you want. This helps keep your data schema clear and informative. You can also include more context or details about what each enum value represents.

How to validate enums in nested objects using Zod?

Zod handles object validation via schemas. For nested object validation, create corresponding nested validation schema.

How to use Zod enums with arrays for complex validation scenarios?

Zod Enums works with arrays to let you create more complex validation rules. e.g. if you had an enum of statuses with values (open, closed, and pending) you can wrap them in z.array(). This validates that each item in an array matches one of these values. Zod also allows you to use array functions to limit the length using .min() and .max(). you can also use .refine() to ensure that all items are unique.

What are the differences between Zod enums and native TypeScript enums?

Zod enums and typescript enums differ in their usage. Zod enums are primarily used for data validation. They provide the validation rules for validating enums. Typescript enums are a language-specific type that defines a set of named constants.

How to handle async refinements with Zod enums?'

Zod allows async logic inside validation. An example is when you have to wait for some asynchronous logic e.g. database call before validation. This is useful for complex validation where simple validation is not enough.

// Zod schema with async refinement
const RoleSchema = z
.string()
.refine((role) => RoleEnum.safeParse(role).success, {
message: "Invalid role",
})
.refine(async (role) => await checkRoleInDb(role), {
message: "Role does not exist in database",
});

//usage
const validateRole = async (role: string) => {
try {
await RoleSchema.parseAsync(role);
console.log("Valid role");
} catch (e) {
console.error("Validation error:", e.errors);
}
};

validateRole("ADMIN"); // Valid role
validateRole("UNKNOWN"); // Validation error: Role does not exist in database

Can Zod enums be used with third-party libraries or existing enums?

Yes, Zod is flexible enough to allow Zod enums to work with third-party libraries. A good example of this is Zod nativeEnum() which works with Typescript's enum. Having the flex can avoid a lot of code duplication.

Sources

  1. Zod Documentation
  2. TypeScript Documentation
logo
Basic Utils

simplify and inspire technology

©2024, basicutils.com