Convert Superstruct’s bare-function API to Zod’s namespaced method chains. Gain access to Zod’s richer validation features, discriminated unions, branded types, and the largest TypeScript schema validation ecosystem.
Install SchemaShift globally and run the migration:
npm install -g schemashift-cli
schemashift migrate ./src -f superstruct -t zod
Superstruct → Zod migration requires a Pro or Team license. View pricing.
import {
object, string, number, integer, boolean,
array, enums, optional, nullable, literal,
union, type Infer
} from 'superstruct';
const UserSchema = object({
name: string(),
email: string(),
age: optional(integer()),
role: enums(['admin', 'user', 'guest']),
tags: array(string()),
bio: nullable(string()),
active: boolean(),
status: literal('active'),
score: union([number(), literal('N/A')]),
});
type User = Infer<typeof UserSchema>;
import { z } from 'zod';
const UserSchema = z.object({
name: z.string(),
email: z.string(),
age: z.number().int().optional(),
role: z.enum(['admin', 'user', 'guest']),
tags: z.array(z.string()),
bio: z.string().nullable(),
active: z.boolean(),
status: z.literal('active'),
score: z.union([z.number(), z.literal('N/A')]),
});
type User = z.infer<typeof UserSchema>;
| Superstruct | Zod | Notes |
|---|---|---|
object({...}) |
z.object({...}) |
Bare function → namespaced method |
string() |
z.string() |
Bare function → namespaced method |
number() |
z.number() |
Bare function → namespaced method |
boolean() |
z.boolean() |
Bare function → namespaced method |
integer() |
z.number().int() |
Separate type in Superstruct; constraint in Zod |
optional(schema) |
schema.optional() |
Wrapper function → chained method |
nullable(schema) |
schema.nullable() |
Wrapper function → chained method |
enums(['a', 'b']) |
z.enum(['a', 'b']) |
Direct mapping with name change |
literal('val') |
z.literal('val') |
Direct mapping |
union([a, b]) |
z.union([a, b]) |
Direct mapping |
array(string()) |
z.array(z.string()) |
Nested bare functions → namespaced |
record(string(), number()) |
z.record(z.string(), z.number()) |
Direct mapping |
tuple([string(), number()]) |
z.tuple([z.string(), z.number()]) |
Direct mapping |
Infer<typeof S> |
z.infer<typeof S> |
Type helper rewritten automatically |
import { string, number } from 'superstruct')
while Zod uses a single namespace (import { z } from 'zod'). SchemaShift rewrites
all import statements and prepends z. to every schema constructor. If you have
variables named string or number in scope, they will not conflict
after migration since Zod uses the z. prefix.
coerce(schema, otherSchema, fn) takes a source type and
transform function. Zod uses z.preprocess(fn, schema) or .transform(fn)
depending on the use case. SchemaShift maps to .transform() with a TODO comment
for complex coercion logic that may need z.preprocess().
define('name', fn) creates custom validators by name.
Zod uses .refine(fn, message) for the same purpose but without named validators.
The validator name is preserved as the error message. Complex define() validators
with custom error objects need manual adjustment.
union() checks each variant sequentially. Zod offers both
z.union() (sequential) and z.discriminatedUnion() (key-based).
SchemaShift converts to z.union() by default. You can manually upgrade to
z.discriminatedUnion() if your schemas share a discriminator field for better
performance and error messages.
size(schema, min, max) and pattern(schema, regex)
as separate wrapper functions. Zod uses chained methods like .min(), .max(),
and .regex(). SchemaShift unwraps these wrappers and chains the equivalent Zod methods.
Run the full migration with dry-run first to preview changes:
# Preview changes without modifying files
schemashift migrate ./src -f superstruct -t zod --dry-run
# Run the actual migration
schemashift migrate ./src -f superstruct -t zod
# Export a diff for code review
schemashift migrate ./src -f superstruct -t zod --dry-run --output-diff changes.patch
# Verbose with HTML report
schemashift migrate ./src -f superstruct -t zod -v --report html
npm install zod and npm uninstall superstruct (after verifying)TODO(schemashift) comments and resolve manuallyoptional() and nullable() wrappers were correctly inverted to chainscoerce() conversions for correct transform logicdefine() → .refine() conversions for error handlingz.union() to z.discriminatedUnion() where applicableinteger() → z.number().int() preserves validation behaviornpx tsc --noEmit to verify TypeScript compilation
Superstruct uses bare function imports like string(), number(),
object() while Zod uses namespaced methods like z.string(),
z.number(), z.object(). SchemaShift automatically adds the
z. namespace prefix to all schema constructors during migration. Import
statements are rewritten from import { string, number } from 'superstruct'
to import { z } from 'zod'.
Superstruct wraps schemas with optional(string()) while Zod chains
.optional() as a method: z.string().optional(). SchemaShift
automatically inverts the nesting, extracting the inner schema and appending
.optional() as a chained method. The same applies to nullable().
No. Superstruct does not have a discriminatedUnion equivalent.
Superstruct’s union() checks each variant in order, while Zod’s
discriminatedUnion() uses a discriminator key for efficient matching.
When migrating union types, SchemaShift converts to z.union() by default.
You can manually upgrade to z.discriminatedUnion() if your schemas have a
common discriminator field.
Convert your Superstruct schemas to Zod automatically with SchemaShift.
Get SchemaShift