Outputs

In addition to validating inputs, zsa allows you to define an output schema using .output, ensuring that the response from your server action adheres to a specified structure. This can help maintain consistency and reliability in your API responses.

Defining an Output Schema

Here's an example of how to create a server action with an output schema:

actions.ts
"use server"

import { createServerAction } from "zsa"
import z from "zod"

export const myOutputAction = createServerAction()
    .input(z.object({
        name: z.string(),
        email: z.string().email()
    }))
    .output(z.object({ 
        message: z.string(), 
        timestamp: z.date() 
    })) 
    .handler(async ({ input }) => {
        // Process the input data and create a response
        return { 
            message: `Hello, ${input.name}! Your email is ${input.email}.`, 
            timestamp: new Date() 
        }; 
    });

In this example, the server action myOutputAction is configured with both an input schema and an output schema. The output method defines the expected structure of the response, ensuring that the handler's return value matches the specified schema.

Dynamic Output 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.

Similar to input schemas, you can also define dynamic output schemas using functions. This allows you to generate the output schema based on data available when the action is invoked, such as the request, response metadata, or context from procedures.

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

  • ctx: The context object generated by any procedures
  • unparsedData: The unparsed data from the handler
  • 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

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

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

export const myAction = createServerAction()
  .output(({ request, responseMeta, ctx, unparsedData }) =>
    z.object({
      // Include extra field in output if user is an admin
      adminData: ctx.user?.isAdmin ? z.string() : z.never(),
      // Set response header based on data
      data: z.number().transform((n) => {
        responseMeta.headers.set('x-data-type', n > 10 ? 'large' : 'small');
        return n;
      }),
    })
  )
  .handler(async ({ ctx }) => {
    // Generate response data based on user role
    return {
      adminData: ctx.user?.isAdmin ? 'super secret info' : undefined,
      data: 42,
    };
  });

In this example, the output schema is dynamically generated based on the user's role from the procedure context. If the user is an admin, an extra adminData field is included in the output schema. The data field also uses a transform to set a response header based on the value.

Some potential use cases for dynamic output schemas:

  • Customizing response structure based on user roles or permissions
  • Conditionally including or excluding fields based on feature flags
  • Setting response headers based on the data being returned

By defining output schemas as functions, you can create server actions with flexible, context-aware responses that adapt to the specific requirements of each invocation.