Inputs

Server actions in zsa can accept validated inputs using Zod schemas. This allows you to ensure that the data passed to your server action matches the expected format.

You can install zod with npm i zod or yarn add zod.

Basic Input Schemas

The most basic way to define an input schema is by passing a Zod schema to the input method when creating a server action:

import { createServerAction } from 'zsa';
import { z } from 'zod';

export const myAction = createServerAction()
  .input(
    z.object({
      name: z.string(),
      age: z.number().min(18),
    })
  )
  .handler(async ({ input }) => {
    // input is validated to match the schema
    console.log(input.name, input.age); 
  });

If the input passed to myAction does not match the defined schema (e.g. name is missing or age is less than 18), a ZSAError will be thrown with code INPUT_PARSE_ERROR.

Dynamic Input Schemas

Although useful in some cases, this should be avoided by default since creating a schema for every request is going to be much more expensive than reusing an existing one.

For more advanced use cases, you can define the input schema using a function that returns a Zod schema. This allows you to dynamically generate the schema based on data available when the action is invoked, such as data from the request or context from procedures.

The input schema function receives an object with the following properties:

  • ctx: The context object generated by any procedures
  • previousSchema: The previous schema of the action if there exists a parent procedure
  • request: The Next.js request object if the action is run through an OpenAPI route handler
  • responseMeta: The response metadata object if the action is run through an OpenAPI route handler
  • previousState: The previous state of the action if the action is run through useActionState

Here's an example of using a function to dynamically generate an input schema:

import { createServerAction } from 'zsa';
import { z } from 'zod';

export const myAction = createServerAction()  
  .input(({ request, ctx, responseMeta, previousState, previousSchema }) => 
    z.object({
      // Require age to be greater than value in request header
      age: z.number().min(parseInt(request.headers.get('min-age') ?? '0')),
      // Only require email if user is not logged in
      email: !ctx.user ? z.string().email() : z.string().email().optional(),
    })
  )
  .handler(async ({ input, ctx }) => {
    // input is generated based on request data and procedure context  
  });

Some potential use cases for dynamic input schemas:

  • Changing required fields based on user roles/permissions
  • Adding extra validation based on feature flags or environment variables
  • Modifying numeric limits based on user subscription level

By defining input schemas as functions, you can create more flexible and context-aware server action inputs. The schema will be evaluated with the latest request data and procedure context each time the action is invoked.