Examples
Transform output at the client boundary
Section titled “Transform output at the client boundary”const getClock = operation({ input: z.void(), output: z.object({ now: z.iso.datetime().transform((value) => new Date(value)), }),});The handler returns { now: new Date().toISOString() }. The server validates the string wire value, and the client receives a Date.
Defined error data
Section titled “Defined error data”const reserve = operation({ input: z.object({ sku: z.string().min(1), quantity: z.int().positive(), }), output: z.object({ reservationId: z.string() }), errors: { OUT_OF_STOCK: { status: 409, message: "Insufficient stock.", data: z.object({ available: z.int().nonnegative() }), }, },});Inside the handler:
throw errors.OUT_OF_STOCK({ data: { available: 0 } });The error becomes a typed domain error only after the client validates the complete response envelope and its data schema.
Custom transport plugin
Section titled “Custom transport plugin”const tracePlugin = defineClientPlugin({ name: "trace", wrapTransport: (next) => (request) => { const headers = new Headers(request.init.headers); headers.set("X-Trace-Source", request.source);
return next({ ...request, init: { ...request.init, headers }, }); },});Plugins can distinguish operation and native Payload requests. Preserve the request metadata and pass the complete request to next.
Void operation
Section titled “Void operation”const removeSession = operation({ input: z.object({ sessionId: z.string() }), output: z.void(),});The handler returns undefined; the endpoint responds with 204 and the client resolves to undefined.
The complete example is type-checked in CI: payload-operations.ts.