A codemod to migrate from runtypes to zod.
- Transforms imports/requires from runtypes to zod
- Converts type definitions to zod schemas
- Updates validation methods (check → parse, guard → safeParse)
- Converts Static to z.infer
- Works with TypeScript files
- Preserves code style and formatting
pnpm dlx runzod <path-to-your-code>
runzod <directory> [options]
--dry
: Do not write to files, just show what would be changed--extensions
: File extensions to process (default: ts,tsx)--help, -h
: Show help message--verbose, -v
: Show more information during processing
# Transform all TypeScript files in the src directory
pnpm dlx runzod ./src
# Dry run to see what would be changed
pnpm dlx runzod ./src --dry
# Transform only specific file extensions
pnpm dlx runzod ./src --extensions ts,tsx
# Show verbose output
pnpm dlx runzod ./src -v
Runtypes | Zod |
---|---|
import { String } from 'runtypes' |
import { z } from 'zod' |
String |
z.string() |
Number |
z.number() |
Boolean |
z.boolean() |
BigInt |
z.bigint() |
Undefined |
z.undefined() |
Null |
z.null() |
Array(String) |
z.array(z.string()) |
Tuple(String, Number) |
z.tuple([z.string(), z.number()]) |
Object({...}) |
z.object({...}) |
Record(String, Number) |
z.record(z.string(), z.number()) |
Union(A, B, C) |
z.union([A, B, C]) |
Intersect(A, B) |
z.intersection([A, B]) |
Literal(x) |
z.literal(x) |
Optional(String) |
z.string().optional() |
String.optional() |
z.string().optional() |
String.withConstraint(...) |
z.string().refine(...) |
String.withBrand("Brand") |
z.string().brand("Brand") |
Type.check(data) |
Type.parse(data) |
Type.guard(data) |
Type.safeParse(data) |
Static<typeof Type> |
z.infer<typeof Type> |
// Before (runtypes)
import { String, Number, Boolean, Array, Object, type Static } from "runtypes";
const User = Object({
name: String,
age: Number,
isActive: Boolean,
tags: Array(String),
});
type User = Static<typeof User>;
if (User.guard(data)) {
console.log(`User ${data.name} is ${data.age} years old`);
}
// After (zod)
import { z } from "zod";
const User = z.object({
name: z.string(),
age: z.number(),
isActive: z.boolean(),
tags: z.array(z.string()),
});
type User = z.infer<typeof User>;
const result = User.safeParse(data);
if (result.success) {
console.log(`User ${result.data.name} is ${result.data.age} years old`);
}
// Before (runtypes)
import { String, withBrand, type Static } from "runtypes";
const UserId = String.withBrand("UserId");
type UserId = Static<typeof UserId>;
// After (zod)
import { z } from "zod";
const UserId = z.string().brand("UserId");
type UserId = z.infer<typeof UserId>;
The codemod handles most common cases, but there are some limitations:
- Complex constraints may need manual adjustment
- The
match
pattern from runtypes needs manual conversion to zod patterns - Recursive types may require adjustments
- Some method chaining might require manual fixes
- Branded type handling might require additional changes
After running the codemod:
- Add "zod" to your dependencies if it's not already there
- Review the transformed files manually
- Update validation logic based on zod's patterns:
runtype.guard(data)
becomesschema.safeParse(data)
(but you'll need to accessresult.data
when success is true)- Error handling differs between libraries
- Run your tests to ensure everything still works
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Test on example files
npm run test:codemod
MIT