Skip to content

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.

Bash
pnpm add @surfjs/zod zod
TypeScript
import { 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_PARAMS error 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:

TypeScript
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:

  1. Calls schema.safeParse(ctx.params) on the incoming params
  2. On failure: sets ctx.error to an INVALID_PARAMS response with field-level Zod issues and halts the pipeline
  3. On success: replaces ctx.params with the parsed and defaulted values, then calls next()

💡 Tip: For per-command Zod validation, use defineZodCommand instead — it converts the schema and validates automatically. zodValidator is 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:

TypeScript
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:

TypeScript
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 |