# How to Use Zod Encode Decode Methods for Bidirectional Data Transformation

> Learn how to use Zod encode decode methods for safe bidirectional data transformation. Safely serialize and deserialize data with full type safety using Zod.

- Repository: [Colin McDonnell/zod](https://github.com/colinhacks/zod)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Zod's encode and decode methods let you define bidirectional transformations between input and output schemas, enabling safe serialization and deserialization with full type safety.**

The `colinhacks/zod` library provides a powerful **codec API** that pairs input and output schemas with reversible transformation logic. Unlike unidirectional transforms, Zod encode decode methods maintain type safety in both directions—parsing raw input into refined types and serializing them back to their original format.

## Understanding Zod's Codec Architecture

Zod implements bidirectional parsing through a direction-aware engine in its core parsing module. When you invoke `encode` or `decode`, the library creates a parse context that determines which transformation direction to apply.

### Core Components in core/parse.ts

The low-level implementation resides in [`packages/zod/src/v4/core/parse.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/parse.ts). This file exports the fundamental `encode` and `decode` functions that power the public API:

- **`decode`** – Forwards to the regular parser with the default forward direction (`packages/zod/src/v4/core/parse.ts#L110-L118`)
- **`encode`** – Forwards to the parser with a `direction: "backward"` flag (`packages/zod/src/v4/core/parse.ts#L96-L108`)

The public façade in [`packages/zod/src/v4/classic/parse.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/classic/parse.ts) re-exports these as top-level `z.encode` and `z.decode` utilities (`packages/zod/src/v4/classic/parse.ts#L35-L41`).

### The Direction Flag

When `encode` creates a parse context with `{ direction: "backward" }`, all transforms check `ctx.direction` to determine which half of a bidirectional transformation to execute. This prevents the "unidirectional transform" errors that occur when trying to serialize data that only has a one-way conversion defined.

## Creating Your First Codec with Zod Encode Decode Methods

A codec combines an input schema, output schema, and transformation callbacks. The builder in [`packages/zod/src/v4/mini/codec.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/mini/codec.ts) wires these components together.

```typescript
import * as z from "zod/mini";
import { en } from "zod/locales";

z.config(en());

// Define bidirectional transformation: ISO string ↔ Date
const isoDateCodec = z.codec(
  z.iso.datetime(), // input schema: validated ISO string
  z.date(),         // output schema: Date instance
  {
    decode: (iso) => new Date(iso),        // forward: string → Date
    encode: (date) => date.toISOString(), // backward: Date → string
  }
);

```

Once defined, you can transform data in both directions:

```typescript
// Decode: raw string → Date
const dateObj = z.decode(isoDateCodec, "2024-01-15T10:30:00.000Z");
// Result: Date object

// Encode: Date → string
const isoString = z.encode(isoDateCodec, dateObj);
// Result: "2024-01-15T10:30:00.000Z"

```

## Practical Usage Patterns

### Basic String to Date Transformation

The ISO datetime codec represents the most common use case—converting between wire formats (strings) and runtime types (Date objects). The input schema validates the string format while the output schema ensures the result is a proper Date instance.

### Async Encode and Decode Operations

When transformations involve asynchronous operations—such as database lookups or external API calls—use the async variants:

```typescript
// Async decode (returns a Promise)
const asyncDecoded = await z.decodeAsync(
  isoDateCodec, 
  "2024-01-15T10:30:00.000Z"
);

// Async encode
const asyncEncoded = await z.encodeAsync(
  isoDateCodec, 
  new Date()
);

```

These functions forward to `_parseAsync` in the core engine, allowing transforms to return Promises that resolve before validation continues.

### Safe Error Handling with safeEncode and safeDecode

For error-handling without try-catch blocks, use the safe variants that return result objects:

```typescript
// Safe decode returns { success, data?, error? }
const safe = z.safeDecode(isoDateCodec, "invalid-date");
if (!safe.success) {
  console.error(safe.error.issues);
  // Handle validation errors
}

// Safe encode
const safeEnc = z.safeEncode(isoDateCodec, new Date("2024-01-01"));
if (safeEnc.success) {
  console.log(safeEnc.data); // ISO string
}

```

These methods wrap the underlying parse calls in error boundaries, returning a discriminated union that TypeScript can narrow.

### Nested Codecs in Object Schemas

Codecs compose within larger schemas, allowing complex transformations at specific fields:

```typescript
const waypointSchema = z.object({
  name: z.string().check(z.minLength(1, "Waypoint name required")),
  difficulty: z.enum(["easy", "medium", "hard"]),
  coordinate: z.codec(
    z.string().check(z.regex(/^-?\d+,-?\d+$/, "Must be 'x,y' format")),
    z.object({ x: z.number(), y: z.number() }),
    {
      decode: (s) => {
        const [x, y] = s.split(",").map(Number);
        return { x, y };
      },
      encode: (obj) => `${obj.x},${obj.y}`,
    }
  ),
});

const raw = {
  name: "Peak",
  difficulty: "hard",
  coordinate: "12,34",
};

// Decode entire object including nested codec
const decoded = z.decode(waypointSchema, raw);
// Encode back to wire format
const encoded = z.encode(waypointSchema, decoded);

```

## Advanced Configuration and Edge Cases

### Handling Unidirectional Transforms

Standard `z.transform` creates unidirectional conversions that only work with `decode`. If you attempt to `encode` through a schema containing a unidirectional transform, Zod throws a `ZodEncodeError` according to the test suite in [`packages/zod/src/v4/classic/tests/transform.test.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/classic/tests/transform.test.ts) (line 356).

Always use `z.codec` with explicit `encode` and `decode` callbacks when you need bidirectional transformation.

### Performance Considerations

The synchronous `encode` and `decode` methods offer better performance than their async counterparts when transformations are pure computations. The core engine in [`packages/zod/src/v4/core/parse.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/parse.ts) optimizes the synchronous path by avoiding Promise overhead.

For high-throughput applications, prefer synchronous codecs and reserve `decodeAsync`/`encodeAsync` for operations that genuinely require I/O.

## Summary

- **Zod encode decode methods** provide bidirectional data transformation through the codec API, coupling input and output schemas with reversible conversion logic.
- The architecture uses a `direction` flag in [`packages/zod/src/v4/core/parse.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/parse.ts) to distinguish between forward (`decode`) and backward (`encode`) transformations.
- Create codecs using `z.codec(inputSchema, outputSchema, { decode, encode })` from the mini API or access top-level utilities via `z.encode` and `z.decode` in the classic API.
- Handle asynchronous transformations with `decodeAsync` and `encodeAsync`, and use `safeDecode` and `safeEncode` for error handling without exceptions.
- Avoid unidirectional `z.transform` when encoding is required, as it triggers a `ZodEncodeError` during backward parsing.

## Frequently Asked Questions

### What is the difference between encode and decode in Zod?

**Decode** transforms raw input data into a refined output type (forward direction), such as parsing an ISO string into a Date object. **Encode** reverses this process, converting the refined output back into the raw input format (backward direction), such as serializing a Date back to an ISO string. The core parser in [`packages/zod/src/v4/core/parse.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/core/parse.ts) distinguishes these operations using a `direction` flag.

### How do I handle errors when using Zod encode decode methods?

Use the `safeDecode` and `safeEncode` utilities to avoid throwing exceptions. These functions return a discriminated union with `success`, `data`, and `error` properties. If `success` is false, the `error` property contains a `ZodError` with detailed issue information. This pattern is particularly useful in API handlers where you need to return structured error responses.

### Can I use async transformations with Zod codecs?

Yes, Zod provides `decodeAsync` and `encodeAsync` for transformations that return Promises. These functions are available in both the core and classic APIs. They forward to the internal `_parseAsync` engine, allowing you to perform database lookups, API calls, or other asynchronous operations during encoding or decoding. Use these when your transformation logic requires I/O.

### What happens if I try to encode a unidirectional transform?

Attempting to `encode` through a schema that uses `z.transform` without a reverse callback throws a `ZodEncodeError`. According to the test suite in [`packages/zod/src/v4/classic/tests/transform.test.ts`](https://github.com/colinhacks/zod/blob/main/packages/zod/src/v4/classic/tests/transform.test.ts), unidirectional transforms only support the forward (`decode`) direction. To enable bidirectional transformation, use `z.codec` with explicit `encode` and `decode` callbacks instead of `z.transform`.