Packages
@surfjs/zod
Zod schema integration for Surf commands
@surfjs/zod#
Use Zod schemas for command parameters instead of the manual ParamSchema format. @surfjs/zod automatically converts Zod types to Surf's native schema format and provides full type inference in handlers.
pnpm add @surfjs/zod zodimport { z } from 'zod'import { createSurf } from '@surfjs/core'import { defineZodCommand } from '@surfjs/zod' const search = defineZodCommand({ description: 'Search products', params: z.object({ query: z.string().describe('Search term'), limit: z.number().int().min(1).max(100).default(10), inStock: z.boolean().optional(), }), run({ query, limit, inStock }) { // All params are fully typed from the Zod schema // query → string // limit → number (default 10) // inStock → boolean | undefined return db.products.search({ query, limit, inStock }) },}) const surf = await createSurf({ name: 'My Store', commands: { search },})💡 Tip: Zod validators run automatically at request time — invalid params return a structured
INVALID_PARAMSerror with field-level messages before your handler is called.
The Zod schema is converted to Surf's ParamSchema at startup — no runtime Zod dependency is shipped to clients. Enum constraints, defaults, descriptions, and nested objects are all preserved in the manifest.
zodValidator — Middleware-based validation
For cases where you need Zod validation on a command that uses the raw ParamSchema format, or for validating per-route schemas in shared middleware, use zodValidator directly:
import { z } from 'zod'import { createSurf } from '@surfjs/core'import { zodValidator } from '@surfjs/zod' const searchSchema = z.object({ query: z.string().min(1, 'Query cannot be empty'), limit: z.number().int().min(1).max(100).optional().default(20),}) const surf = await createSurf({ name: 'My Store', // zodValidator runs as global middleware — validates params before the handler middleware: [zodValidator(searchSchema)], commands: { search: { description: 'Search products', params: { query: { type: 'string', required: true }, limit: { type: 'number' }, }, run: async ({ query, limit }) => db.search(query, { limit }), }, },})zodValidator returns a SurfMiddleware that:
- Calls
schema.safeParse(ctx.params)on the incoming params - On failure: sets
ctx.errorto anINVALID_PARAMSresponse with field-level Zod issues and halts the pipeline - On success: replaces
ctx.paramswith the parsed and defaulted values, then callsnext()
💡 Tip: For per-command Zod validation, use
defineZodCommandinstead — it converts the schema and validates automatically.zodValidatoris a global middleware and runs on all commands.
When to use zodValidator vs defineZodCommand
| | defineZodCommand | zodValidator |
|--|-------------------|---------------|
| Param schema | Zod (auto-converted to ParamSchema) | Raw ParamSchema (you write it manually) |
| Validation | Automatic at request time | Middleware-based |
| Type inference | ✅ Full inference in handler | ❌ Handler params are untyped |
| Best for | New commands | Existing commands, shared validation logic |
zodToSurfParams — Schema conversion utility
Convert a Zod object schema to Surf's native ParamSchema format. Used internally by defineZodCommand — but useful when you need the conversion as a standalone step:
import { z } from 'zod'import { zodToSurfParams } from '@surfjs/zod' const schema = z.object({ query: z.string().describe('Search term'), limit: z.number().int().min(1).max(100).default(10), category: z.enum(['electronics', 'clothing', 'books']).optional(),}) const surfParams = zodToSurfParams(schema)// => {// query: { type: 'string', required: true, description: 'Search term' },// limit: { type: 'number', required: false, default: 10 },// category: { type: 'string', enum: ['electronics', 'clothing', 'books'], required: false },// }Type mapping
| Zod type | Surf ParamSchema type |
|----------|------------------------|
| z.string() | "string" |
| z.number() | "number" |
| z.boolean() | "boolean" |
| z.enum([...]) | "string" with enum array |
| z.object({...}) | "object" with nested properties |
| z.array(...) | "array" with items |
| .optional() | required: false |
| .default(x) | required: false, default: x |
| .describe('...') | description: '...' |
convertZodType
For converting a single Zod type (not a full z.object), use the lower-level convertZodType:
import { convertZodType } from '@surfjs/zod' const paramSchema = convertZodType(z.string().describe('A product SKU'))// => { type: 'string', description: 'A product SKU' }This is useful for building custom command definition wrappers or for programmatic schema generation.
Exports Summary#
| Export | Type | Description |
|--------|------|-------------|
| defineZodCommand | function | Define a Surf command with a Zod param schema |
| zodValidator | function | Create a middleware that validates params with a Zod schema |
| zodToSurfParams | function | Convert a z.object schema to ParamSchema |
| convertZodType | function | Convert a single Zod type to ParamSchema |
| ZodCommandConfig | interface | Config type for defineZodCommand |