How to Define Custom Actions with defineAction and Validate Them with Zod in Agent-Native

Use defineAction from @agent-native/core to declare server-side functions, passing a Zod schema to the schema property for automatic input validation and an optional outputSchema for return-value validation.

Agent-Native, the open-source framework from BuilderIO for building AI-powered applications, provides a declarative API for exposing server-side functions to both UI components and AI agents. The defineAction helper in packages/core/src/action.ts serves as the central factory for creating type-safe actions with built-in Zod validation, ensuring runtime safety and consistent JSON Schema generation for Claude tool descriptions.

Understanding the defineAction API Structure

The defineAction factory function builds an ActionDefinition object that encapsulates metadata, validation logic, and the implementation. According to the source code in packages/core/src/action.ts (starting at line 607), the options object accepts several key properties:

Core Properties

  • description – Human-readable text displayed in the UI and embedded in the Claude tool description.
  • schema – A Zod object describing the input shape. When provided, the runtime automatically validates payloads before executing the run function.
  • outputSchema (optional) – A Zod schema validating the returned data. If the action returns invalid data, the framework responds based on the outputErrorStrategy setting.
  • run – The actual implementation receiving a fully-typed argument object (inferred from the Zod schema) that may be async.
  • http / readOnly / toolCallable / agentTool – Metadata flags governing HTTP exposure, UI permissions, and agent-tool bridge visibility.

How Validation Works Under the Hood

Agent-Native wraps your run function with validation layers when schemas are present.

Input Validation

If options.schema is provided and contains the "~standard" marker (indicating a Standard Schema), the factory creates an input-validated wrapper using wrapWithValidation (around line 630 in action.ts). This wrapper executes schema.safeParse on incoming payloads. Validation errors are transformed into clear messages that include the expected signature, preventing malformed data from reaching your business logic.

Output Validation

When options.outputSchema is supplied, a second wrapper (wrapWithOutputValidation) validates the return value. The outputErrorStrategy parameter (strict, warn, or fallback) determines whether the framework throws an error, logs a warning, or replaces the result with options.outputFallback.

The complete flow is implemented in packages/core/src/action.ts between lines 635-660:

const inputValidatedRun = hasSchema
  ? wrapWithValidation(options.schema, options.run, toolParameters)
  : options.run;

const run = hasOutputSchema
  ? wrapWithOutputValidation(
      options.outputSchema,
      inputValidatedRun,
      outputErrorStrategy,
      options.outputFallback,
      options.description,
    )
  : inputValidatedRun;

Creating a Zero-Input Action

Some actions, such as retrieving the current application state, require no parameters. The view-screen action in templates/videos/actions/view-screen.ts demonstrates this pattern using an empty Zod object:

import { defineAction } from "@agent-native/core";
import { readAppState } from "@agent-native/core/application-state";
import { z } from "zod";

export default defineAction({
  description:
    "See what the user is currently looking at on screen. Returns the current view and composition details.",
  schema: z.object({}),               // No input parameters required
  http: false,                        // Internal tool, not exposed as HTTP endpoint
  run: async () => {
    const navigation = await readAppState("navigation");
    const screen: Record<string, unknown> = {};

    if (navigation) screen.navigation = navigation;
    const nav = navigation as any;

    if (nav?.compositionId) {
      screen.context = {
        view: "composition",
        compositionId: nav.compositionId,
        folderId: nav.folderId ?? null,
        folderName: nav.folderName ?? null,
        hint: "User is editing a composition",
      };
    } else {
      screen.context = {
        view: "studio-home",
        hint: "User is on the studio home page",
      };
    }

    return Object.keys(screen).length === 0
      ? "No application state found. Is the app running?"
      : JSON.stringify(screen, null, 2);
  },
});

This action validates that no extraneous inputs are provided while returning a JSON string describing the current UI state.

Creating Validated Actions with Input and Output Schemas

For mutations requiring strict type safety, define both input and output schemas. The following example illustrates a folder creation action with validation:

import { defineAction } from "@agent-native/core";
import { z } from "zod";

export default defineAction({
  description: "Create a new folder in the user's library.",
  schema: z.object({
    name: z.string().min(1, "Folder name cannot be empty"),
    parentId: z.string().optional(),
  }),
  outputSchema: z.object({
    folderId: z.string(),
  }),
  http: { method: "POST", path: "/api/folder" },
  run: async ({ name, parentId }) => {
    const folderId = await createFolderInDb(name, parentId);
    return { folderId };
  },
});

Execution flow for this action:

  1. The request body is parsed and validated against the input Zod schema.
  2. If validation passes, the run function executes with fully typed arguments.
  3. The returned object { folderId } is validated against outputSchema.
  4. Any output violation triggers the configured outputErrorStrategy (defaulting to warn).

Advanced Configuration and Metadata

Beyond validation, defineAction supports additional flags to control exposure and behavior:

  • http – Configures the action as an HTTP endpoint (e.g., { method: "POST", path: "/api/action" }) or disables HTTP exposure with false.
  • readOnly – Automatically inferred from the HTTP method when not explicitly set, affecting UI permission hints.
  • toolCallable and agentTool – Boolean flags controlling whether the action appears in the agent's tool catalogue.
  • audit – Hooks for logging and observability integrated into the action lifecycle.

These properties are processed alongside validation wrappers in the defineAction implementation to generate the final action definition consumed by the Agent-Native runtime.

Summary

  • defineAction in packages/core/src/action.ts (line 607+) is the central factory for creating type-safe, validated actions in Agent-Native.
  • Supply a Zod schema to the schema property to enable automatic input validation before your run function executes.
  • Use outputSchema with outputErrorStrategy to validate return values and handle mismatches via strict errors, warnings, or fallback values.
  • The framework uses wrapWithValidation and wrapWithOutputValidation to inject Zod's safeParse logic, converting validation errors into agent-friendly messages.
  • Actions can be internal tools (http: false) or HTTP endpoints, with metadata flags controlling visibility to the AI agent and UI.

Frequently Asked Questions

What happens if Zod validation fails in defineAction?

When input validation fails, the wrapping function catches the Zod error and returns a clear message indicating the expected schema shape, preventing the run function from executing with malformed data. Output validation failures are handled according to the outputErrorStrategy setting (strict, warn, or fallback).

Can I use output validation without input validation in Agent-Native?

Yes. While the schema property is optional, you can provide outputSchema alone to validate return values even if the action accepts no parameters or performs manual input handling. The wrappers are applied independently based on the presence of each schema.

How does defineAction generate JSON Schema for Claude tools?

When you provide a Zod schema, defineAction detects the Standard Schema marker ("~standard") and converts the Zod object to JSON Schema format. This ensures the AI agent receives the exact input contract, with descriptions and types matching your TypeScript definitions.

Is defineAction only for creating HTTP endpoints?

No. By setting http: false, you create internal actions accessible only to the UI and AI agent without exposing a public HTTP route. This is useful for state-reading operations or sensitive utilities that should remain within the application boundary.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →