Schema Type System
April 15, 2026 ยท View on GitHub
This module provides a flexible, TypeScript-friendly schema/type system for runtime validation, parsing, and type inference. It is inspired by libraries like valita and implements the Standard Schema specification.
Features
- Define schemas for primitives, objects, arrays, tuples, unions, literals, records, and functions
- Parse and validate data at runtime
- Type inference for static TypeScript types
- Schema transformation:
.pick(),.omit(),.partial(),.required(),.extend() - Serialize/deserialize schemas to/from JSON
- Export schemas to TypeScript interfaces, Swift structs, or JSON Schema
- Parse environment variables and CLI arguments against a schema
- Standard Schema V1 compatible - works with tRPC, TanStack Form/Router, Hono, and other compatible libraries
Standard Schema Compatibility
All zeed schemas implement the StandardSchemaV1 interface, making them compatible with a growing ecosystem of tools and frameworks. This means you can use zeed schemas anywhere that accepts standard-schema-compliant validators.
Using with Standard Schema
Every schema has a ~standard property that provides the standard interface:
import { z } from 'zeed'
const schema = z.object({
name: z.string(),
age: z.number(),
})
// Access standard schema interface
const result = schema['~standard'].validate({ name: 'Alice', age: 30 })
if (result.issues) {
console.error('Validation errors:', result.issues)
}
else {
console.log('Valid data:', result.value)
}
Standard Schema Properties
The ~standard property provides:
version: Always1(the specification version)vendor:'zeed'(identifies the schema library)validate(value): Synchronous validation function that returns either success or failuretypes: Type metadata for TypeScript type inference
Type Inference
import type { StandardSchemaV1 } from 'zeed'
import { z } from 'zeed'
const schema = z.object({
email: z.string(),
age: z.number().optional(),
})
// Extract input/output types via standard schema helpers
type Input = StandardSchemaV1.InferInput<typeof schema>
type Output = StandardSchemaV1.InferOutput<typeof schema>
// Or use zeed's own type inference
type User = z.infer<typeof schema>
Compatible Libraries
Zeed schemas work with any library that supports Standard Schema, including tRPC, TanStack Form, TanStack Router, Hono and more. See the full list.
Generic Validation Function Example
import type { StandardSchemaV1 } from 'zeed'
import { z } from 'zeed'
function standardValidate<T extends StandardSchemaV1>(
schema: T,
data: unknown,
): StandardSchemaV1.InferOutput<T> {
const result = schema['~standard'].validate(data)
if (result.issues)
throw new Error(`Validation failed: ${JSON.stringify(result.issues)}`)
return result.value
}
const userSchema = z.object({ name: z.string() })
const user = standardValidate(userSchema, { name: 'Alice' })
Basic Usage
Primitives
import { z } from 'zeed'
const name = z.string()
const age = z.number()
const count = z.int()
const flag = z.boolean()
const nothing = z.none() // undefined | null
const whatever = z.any()
// Numeric aliases for number()
const x = z.float()
const y = z.double()
const r = z.real()
Objects
import { z } from 'zeed'
const user = z.object({
name: z.string(),
age: z.number().optional(),
})
// Type: { name: string; age?: number }
Arrays and Tuples
import { z } from 'zeed'
const tags = z.array(z.string())
// Type: string[]
const pair = z.tuple([z.string(), z.number()])
// Type: [string, number]
Records
import { z } from 'zeed'
const scores = z.record(z.number())
// Type: Record<string, number>
Unions and Literals
import { z } from 'zeed'
const status = z.union([
z.literal('active'),
z.literal('inactive'),
])
// Type: 'active' | 'inactive'
const role = z.enum(['admin', 'user', 'guest'])
// Type: 'admin' | 'user' | 'guest'
// Note: z.enum is an alias for stringLiterals
Type Inference
import { z } from 'zeed'
const user = z.object({ name: z.string(), age: z.number().optional() })
type User = z.infer<typeof user>
// { name: string; age?: number }
Parsing
import { z } from 'zeed'
const user = z.object({ name: z.string(), age: z.number().optional() })
const parsed = user.parse({ name: 'Alice', age: 30 })
Optional and Default Values
import { z } from 'zeed'
const score = z.number().optional().default(0)
// Type: number | undefined (default: 0)
// Default can also be a function
const id = z.string().default(() => crypto.randomUUID())
Metadata and Descriptions
import { z } from 'zeed'
const name = z.string().meta({ desc: 'The user name' })
const age = z.number().describe('Age in years')
Function and RPC Types
import { z } from 'zeed'
const add = z.func([z.number(), z.number()], z.number())
// Type: (a: number, b: number) => number
const rpcCall = z.rpc(z.object({ id: z.string() }), z.number())
// Type: (info: { id: string }) => number | Promise<number>
Object Schema Transformations
Object schemas support composition methods similar to Zod:
import { z } from 'zeed'
const user = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
age: z.number(),
})
// Extend with additional fields
const userWithRole = user.extend({ role: z.string() })
// Pick a subset of fields
const userName = user.pick({ id: true, name: true })
// Omit fields
const userWithoutEmail = user.omit({ email: true })
// Make all (or selected) fields optional
const partialUser = user.partial()
const partialByKey = user.partial({ age: true })
// Make all (or selected) fields required
const requiredUser = partialUser.required()
Serialization and Deserialization
Serialize schemas to plain JSON and reconstruct them later. Useful for sending schemas over the network or persisting them.
import { deserializeSchema, serializeSchema, z } from 'zeed'
const userSchema = z.object({
name: z.string(),
age: z.number().optional(),
role: z.enum(['admin', 'user']).default('user'),
})
const serialized = serializeSchema(userSchema)
const jsonString = JSON.stringify(serialized)
const deserialized = deserializeSchema(JSON.parse(jsonString))
const user = deserialized.parse({ name: 'Alice', role: 'admin' })
Note: Function defaults (e.g., .default(() => 'value')) cannot be serialized and are omitted. Only static default values are preserved.
Parsing Environment Variables
Map a flat object schema onto process.env. Field names are converted from camelCase to UPPER_SNAKE_CASE.
import { parseSchemaEnv, stringFromSchemaEnv, z } from 'zeed'
const envSchema = z.object({
port: z.int().default(3000).meta({ envDesc: 'Server port' }),
dbUrl: z.string().meta({ envDesc: 'Database connection URL' }),
debug: z.boolean().default(false),
secret: z.string().meta({ envPrivate: true }),
})
const env = parseSchemaEnv(envSchema)
// Reads PORT, DB_URL, DEBUG, SECRET from process.env
// Generate a .env template (private fields are hidden unless showPrivate=true)
const template = stringFromSchemaEnv(envSchema)
Supported meta fields: envDesc, envPrivate, envSkip.
Parsing CLI Arguments
import { helpSchemaArgs, parseSchemaArgs, z } from 'zeed'
const argsSchema = z.object({
input: z.string().meta({ argShort: 'i', argDesc: 'Input file' }),
verbose: z.boolean().default(false).meta({ argShort: 'v' }),
count: z.int().default(1),
})
const [args, rest] = parseSchemaArgs(argsSchema) // reads process.argv
const help = helpSchemaArgs(argsSchema)
Supported meta fields: argShort, argDesc.
Exporting Schemas
TypeScript Interface
import { schemaExportTypescriptInterface, z } from 'zeed'
const user = z.object({
name: z.string(),
age: z.int().optional(),
})
const ts = schemaExportTypescriptInterface(user, 'User')
// export interface User { name: string; age?: number }
Swift Struct
import { schemaExportSwiftStruct, z } from 'zeed'
const user = z.object({
name: z.string(),
age: z.int().optional(),
})
const swift = schemaExportSwiftStruct(user, 'User')
Supported meta fields: swiftName, swiftProtocol, swiftDesc, swiftDefault.
JSON Schema
import { schemaExportJsonSchema, schemaExportJsonSchemaString, z } from 'zeed'
const user = z.object({
name: z.string(),
age: z.number().optional(),
})
const jsonSchema = schemaExportJsonSchema(user)
const jsonSchemaStr = schemaExportJsonSchemaString(user)
API Reference
Primitives
z.string(),z.number(),z.int(),z.boolean(),z.none(),z.any()z.float(),z.double(),z.real()- aliases forz.number()
Composites
z.object({...}),z.array(type),z.tuple([...]),z.record(type)z.union([...])z.literal(value),z.stringLiterals([...]),z.enum([...])z.func(args, ret),z.rpc(info, ret)
Type methods
.optional(),.default(value | fn).meta({ desc, ... }),.describe(msg).parse(obj)- Object only:
.extend({...}),.pick({...}),.omit({...}),.partial(),.partial({...}),.required(),.required({...})
Type inference helpers
z.infer<typeof schema>
Serialization
serializeSchema(schema)- Convert a schema to a plain JSON objectdeserializeSchema(json)- Reconstruct a schema from a JSON object
Parsing helpers
schemaCreateObject(schema)- Build an object from defaultsschemaParseObject(schema, obj, opt?)- Parse and coerce an object against a schemaschemaValidateObject(schema, obj, opt?)- Validate without coercion, returns issues
Environment and CLI
parseSchemaEnv(schema, opt?),stringFromSchemaEnv(schema, prefix?, commentOut?, showPrivate?)parseSchemaArgs(schema, argv?),helpSchemaArgs(schema)
Exporters
schemaExportTypescriptInterface(schema, name?)schemaExportSwiftStruct(schema, name?)schemaExportJsonSchema(schema),schemaExportJsonSchemaString(schema)
Utilities
isSchemaObject(schema),isSchemaObjectFlat(schema)isSchemaOptional(schema),isSchemaDefault(schema),isSchemaPrimitive(schema)
Notes
z.string()and the barestring()export are functionally equivalent, butz.string()is preferred for consistency and clarity.- Each schema definition results in a corresponding TypeScript type, extractable via
z.infer<typeof schema>.