Skip to main content

interoperability Kanon ↔ Zod Interoperability

Real compatibility data. No guesswork. Analyzed against Zod v4 Classic.

Zod v4 Classic vs Mini

Zod v4 offers two APIs:

  • Classic: Method chaining (.optional(), .nullable()). This page covers Classic.
  • Mini: Pure functions like Kanon's native API (optional(schema), nullable(schema))

If you use Zod Mini, consider using Kanon's direct imports instead of the z shim: the syntax is nearly identical.

TL;DR

Core API compatible. Primitives, composites, unions, wrappers, coercion, refinements, and transforms work with the z shim.

MetricValue
Primitives15/15 (100%)
Composites6/6 (100%)
Operators3/3 (100%)
Object Modes3/3 (100%)
Migration EffortImport changes only (for most schemas)
Bottom line

If you use primitives, objects, arrays, unions, and basic wrappers, you're covered. Change your imports and you're done.


About this page

Kanon's native API is designed for optimal tree-shaking: pure functions, direct imports, no class overhead. We recommend using direct imports for production apps.

However, for smooth migration from Zod, Kanon provides a z shim with a 1:1 Zod-compatible API:

// Just swap your import
import { z } from "pithos/kanon/helpers/as-zod.shim";

// Your existing Zod code works as-is
const schema = z.object({
name: z.string(),
age: z.number().optional(),
});

This page focuses on the z shim: all compatibility tables below use the z. syntax. Once migrated, you can gradually refactor to direct imports for maximum bundle optimization.


checkmark What Kanon handles the same

Click to expand each category and see the supported features:

Primitives (100%)15/15

z.string(), z.number(), z.boolean(), z.bigint(), z.date(), z.symbol(), z.undefined(), z.null(), z.void(), z.any(), z.unknown(), z.never(), z.literal(), z.enum(), z.nativeEnum()

Composites (100%)6/6

z.object(), z.array(), z.tuple(), z.record(), z.map(), z.set()

Operators (100%)3/3

z.union(), z.intersection(), z.discriminatedUnion(), .or(), .and()

Wrappers (100%)7/7

.optional(), .nullable(), .nullish(), .default(), .readonly(), z.lazy(), .catch()

Object Transforms (100%)5/5

.partial(), .required(), .pick(), .omit(), .keyof()

Object Modes (100%)3/3

z.object() (strip by default), .strict(), .passthrough()

Coercion (100%)5/5

z.coerce.string(), z.coerce.number(), z.coerce.boolean(), z.coerce.bigint(), z.coerce.date()

Refinements (100%)2/2

.refine(), .superRefine()

Transforms (100%)2/2

.transform(), .array()

String Constraints (100%)10/10

.min(), .max(), .length(), .email(), .url(), .uuid(), .regex(), .includes(), .startsWith(), .endsWith()

Number Constraints (100%)10/10

.min(), .max(), .int(), .positive(), .negative(), .gt(), .gte(), .lt(), .lte(), .multipleOf()

Code Examples

With the z shim, your Zod code works as-is. Just change the import.

Basic Schema

// Zod
import { z } from "zod";

// Kanon (just change the import!)
import { z } from "pithos/kanon/helpers/as-zod.shim";

// Same code works in both
const userSchema = z.object({
id: z.number(),
name: z.string(),
active: z.boolean(),
});

With Constraints

Kanon supports the same constraint methods as Zod, including min/max lengths, positive numbers, and email validation:

import { z } from "pithos/kanon/helpers/as-zod.shim";

const productSchema = z.object({
sku: z.string().min(3).max(10),
price: z.number().positive().max(9999),
email: z.string().email(),
});

With Optional/Nullable

Optional, nullable, and nullish modifiers work identically to Zod, no code changes needed:

import { z } from "pithos/kanon/helpers/as-zod.shim";

const profileSchema = z.object({
username: z.string(),
bio: z.string().optional(),
avatar: z.string().nullable(),
nickname: z.string().nullish(),
});

Union Types

Kanon supports union types through z.union() and literal values, just like Zod:

import { z } from "pithos/kanon/helpers/as-zod.shim";

const statusSchema = z.union([
z.literal("pending"),
z.literal("approved"),
z.literal("rejected"),
]);

const responseSchema = z.object({
data: z.union([z.string(), z.number()]),
});

warning What Kanon doesn't support

The z shim focuses on core validation. Some Zod features are not available and likely never will be.

Not in the z shim

FeatureWorkaround
.pipe()Chain schemas manually
.brand()Use type assertions
z.instanceof()Use .refine(v => v instanceof Class)
z.preprocess()Use .transform() before validation
z.custom()Use .refine() or .superRefine()

String format validators (Zod v4)

FeatureWorkaround
z.email(), z.uuid(), z.url()Use z.string().email(), etc.
z.jwt(), z.ipv4(), z.base64()Use z.string().regex()
Why Kanon doesn't cover 100% of Zod

Kanon deliberately focuses on essential features: the ones you actually use in 90%+ of projects.

100% covered:

  • Primitives, objects, arrays, tuples, unions, intersections
  • Optional, nullable, default, transforms, refinements
  • String constraints (min, max, regex, email, url...)

Not included by design:

  • Specialized ID formats (CUID, ULID, KSUID, XID, NanoID...)
  • Network validators (CIDR ranges, E.164 phone numbers...)
  • Hash validators (MD5, SHA256, SHA512 in hex/base64...)
  • ISO 8601 duration parsing, extended datetime formats...

These are edge cases. Zod bundles them by default, which adds weight even if you never use them. Kanon doesn't. If you need one of these, z.string().regex() works perfectly.


sparkles What Kanon adds

Beyond Zod compatibility, Kanon brings unique features:

FeatureDescription
sparkles JIT CompilationAutomatic compilation to optimized validators (2-10x faster)
bundle size Perfect Tree-ShakingPure functions, no class overhead, smallest possible bundle
target strictObject() / looseObject()Explicit object validation modes
flash parseBulk()Optimized batch validation for arrays
import Direct importsImport only what you use for maximum tree-shaking

Ready to migrate? The complete step-by-step migration guide is in the Kanon module documentation: install, swap imports, handle edge cases, and optimize with direct imports. Go to migration guide →

Not sure which library fits your use case? See the comparison overview.

After migration

Once you've migrated with the z shim, you can gradually refactor to direct imports for even smaller bundles. See the API documentation for the native Kanon API.


Related