
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Write Zod v4 schemas, run them in Zod v3 environments
Many popular libraries still depend on Zod v3 (like @modelcontextprotocol/sdk), but you want to use the latest Zod v4 features and stay current with the ecosystem. Version conflicts force you to choose between modern schemas and library compatibility.
zodown converts Zod v4 schemas to functionally equivalent Zod v3 schemas at runtime, preserving all validations, refinements, and type safety. Write modern code, maintain compatibility.
npm install zodown
# or
pnpm add zodown
# or
yarn add zodown
Note: zodown includes Zod v4 built-in and handles Zod v3 conversion internally. You don't need to install Zod separately!
import { z, zodown } from 'zodown' // Includes Zod v4!
// Write modern Zod v4 schemas
const UserSchema = z.object({
email: z.string().email(),
age: z.number().int().positive(),
role: z.enum(['admin', 'user']),
metadata: z.record(z.unknown()).optional(),
})
// Convert for Zod v3 compatibility
const v3Schema = zodown(UserSchema)
// Use with libraries that require Zod v3
import { Client } from '@modelcontextprotocol/sdk'
const client = new Client({
schema: v3Schema, // Works perfectly!
})
✅ Complete Type Support - All Zod types including primitives, objects, arrays, unions, intersections, and more
✅ Validation Preservation - All refinements, transforms, and custom validations are maintained
✅ Type Safety - Full TypeScript support with proper type inference
✅ Circular References - Handles recursive schemas with WeakMap caching
✅ Zero Configuration - Just wrap your schema with zodown()
✅ Lightweight - ~1.3KB gzipped converter logic
zodown supports all Zod v4 types:
import { z, zodown } from 'zodown'
const ProductSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
price: z.number().positive(),
tags: z.array(z.string()),
})
const v3Product = zodown(ProductSchema)
import { z, zodown } from 'zodown'
const PasswordSchema = z
.string()
.min(8)
.regex(/[A-Z]/, 'Must contain uppercase')
.regex(/[0-9]/, 'Must contain number')
.refine((val) => !commonPasswords.includes(val), 'Too common')
const v3Password = zodown(PasswordSchema)
import { z, zodown, ZodType } from 'zodown'
const CategorySchema: ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(CategorySchema).optional(),
})
)
const v3Category = zodown(CategorySchema)
import { z, zodown } from 'zodown'
const DateSchema = z
.string()
.transform((str) => new Date(str))
.refine((date) => !isNaN(date.getTime()), 'Invalid date')
const v3Date = zodown(DateSchema)
zodown(schema)Converts a Zod v4 schema to a Zod v3 schema.
schema - Any Zod v4 schemaInferDowngraded<T>Type helper for extracting the inferred type from a downgraded schema.
import type { InferDowngraded } from 'zodown'
const Schema = z.object({ name: z.string() })
type User = InferDowngraded<typeof Schema> // { name: string }
While zodown handles most Zod v4 features, there are some limitations due to architectural differences between v3 and v4:
.refine() and .superRefine() on base types - In Zod v4, these are compiled into internal check functions that cannot be reverse-engineered. The base schema is returned without the refinement.
Branded types - Zod v4 implements branding as a compile-time TypeScript feature with no runtime representation, while v3 expects runtime ZodBranded instances. Branded schemas are returned as their base type.
Variable-length tuples with optional elements - Zod v4 allows tuples like [string, number?] to have variable length, but v3 requires fixed length with undefined for optional elements.
If you need these features, consider:
// These v4 features have limitations:
// ❌ Refinements are lost
const v4Refined = z.number().refine((n) => n % 2 === 0)
const v3Refined = zodown(v4Refined) // Returns ZodNumber without refinement
// ❌ Brands become base types
const v4Branded = z.string().brand<'UserId'>()
const v3Branded = zodown(v4Branded) // Returns ZodString, not ZodBranded
// ❌ Optional tuple elements need all positions
const v4Tuple = z.tuple([z.string(), z.number().optional()])
const v3Tuple = zodown(v4Tuple)
v3Tuple.parse(['hi', undefined]) // ✅ Works
v3Tuple.parse(['hi']) // ❌ Fails - v3 needs fixed length
@modelcontextprotocol/sdk with modern Zod schemasContributions are welcome! Please feel free to submit a Pull Request. See CONTRIBUTING.md for details.
MIT © Justin Schroeder
Built with necessity for the Model Context Protocol SDK and the many other libraries still on Zod v3.
FAQs
Write Zod v4 schemas, run them in Zod v3 environments
The npm package zodown receives a total of 3,248 weekly downloads. As such, zodown popularity was classified as popular.
We found that zodown demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.