Kanon ↔ Zod Interoperability
Real compatibility data. No guesswork. Analyzed against Zod v4 Classic.
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.
| Metric | Value |
|---|---|
| Primitives | 15/15 (100%) |
| Composites | 6/6 (100%) |
| Operators | 3/3 (100%) |
| Object Modes | 3/3 (100%) |
| Migration Effort | Import changes only (for most schemas) |
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.
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()]),
});
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
| Feature | Workaround |
|---|---|
.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)
| Feature | Workaround |
|---|---|
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.
What Kanon adds
Beyond Zod compatibility, Kanon brings unique features:
| Feature | Description |
|---|---|
JIT Compilation | Automatic compilation to optimized validators (2-10x faster) |
Perfect Tree-Shaking | Pure functions, no class overhead, smallest possible bundle |
strictObject() / looseObject() | Explicit object validation modes |
parseBulk() | Optimized batch validation for arrays |
Direct imports | Import 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.
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
- Kanon vs Zod — Full comparison: philosophy, API, migration
- Zygos ↔ Neverthrow Interoperability — Another drop-in replacement story
- Equivalence Table — Full library equivalence across all modules
- Kanon Module Guide — Full module documentation
Perfect Tree-Shaking
Direct imports