How to Define Actions with Standard Schema (Zod) for Agent-Native

To define actions with Standard Schema (Zod) in Agent-Native, import defineAction from @agent-native/core/action, provide a Zod object schema via the schema property, and implement the async handler function which receives validated, typed input.

Agent-Native actions serve as the single source of truth for both UI calls (useActionQuery / useActionMutation) and LLM-driven tool calls in the BuilderIO/agent-native framework. Every action declared with defineAction must include a Zod schema that describes accepted input parameters, enabling automatic validation across HTTP requests, Agent Tool API calls, and CLI invocations.

Understanding the defineAction API

The defineAction function imported from @agent-native/core/action wraps an async handler and attaches metadata that governs how the action is exposed and validated. At minimum, an action requires a schema property containing a Zod validator and a handler function that receives the validated input.

The complete signature follows this pattern:

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

export default defineAction({
  // Optional: expose as REST endpoint
  http: { method: "POST" },
  // Required: Zod schema describing the payload
  schema: z.object({
    name: z.string().min(1),
    type: z.enum(["manual", "generic", "github"]),
  }),
  // Required: implementation receiving typed data
  async handler({ input, ctx }) {
    // input is fully typed according to schema
    return { result: input.name };
  },
});

The framework validates input against schema before invoking handler. If validation fails, the runtime automatically returns a 400 HTTP response for web requests or a structured error object for tool calls, preventing invalid data from reaching your business logic.

Creating Your First Zod Schema Action

For agent-only actions that should not expose an HTTP endpoint, set http: false and define your schema inline.

// src/actions/ping.ts
import { defineAction } from "@agent-native/core/action";
import { z } from "zod";

export default defineAction({
  http: false,
  schema: z.object({
    message: z.string().min(1),
  }),
  async handler({ input }) {
    // input is typed as { message: string }
    return { echo: input.message };
  },
});

This configuration ensures the action is callable only by the LLM agent or internal UI methods, not via external HTTP requests. The schema guarantees that message exists and contains at least one character before the handler executes.

Leveraging Shared Schemas for Consistency

The repository maintains centralized reusable schemas in _schemas.ts files to ensure validation consistency across actions. According to the source code in templates/brain/actions/_schemas.ts, common patterns include ID validation, enumerated types, and boolean preprocessing.

Key shared schemas include:

// templates/brain/actions/_schemas.ts
export const idSchema = z.string().min(1);

export const sourceProviderSchema = z.enum([
  "manual", "generic", "clips", "slack", "granola", "github",
]);

export const booleanishSchema = z.preprocess((value) => {
  if (typeof value !== "string") return value;
  const normalized = value.trim().toLowerCase();
  return ["true","1","yes","on"].includes(normalized) ? true :
         ["false","0","no","off"].includes(normalized) ? false : value;
}, z.boolean());

export const jsonRecordSchema = z.preprocess(
  parseJsonCliInput,
  z.record(z.string(), z.unknown()).default({}),
);

Import these utilities to maintain type safety across your action definitions while avoiding repetitive schema declarations.

Configuring HTTP Exposure vs. Agent-Only Actions

The http property in defineAction determines whether and how an action exposes a REST endpoint. This configuration allows the same action file to serve both UI components and LLM tool calls.

  • Read-only actions: Add http: { method: "GET" } to expose via HTTP GET
  • Write actions: Omit http or use http: { method: "POST" } for default POST behavior
  • Agent-only: Set http: false to prevent HTTP exposure entirely

As implemented in templates/videos/actions/update-design-system.ts, actions can selectively expose functionality while maintaining the same Zod validation layer for both HTTP and agent invocations.

Validation Flow and Error Handling

The Agent-Native runtime performs validation exactly once per call, immediately upon receiving the request. The flow proceeds as follows:

  1. Incoming request arrives via HTTP, CLI, or LLM tool API
  2. The runtime extracts the payload and executes the Zod schema validation
  3. On success: The handler receives a fully typed input object and executes business logic
  4. On failure: A ZodError transforms into a standardized error payload—400 status for HTTP or { error: ... } for tool calls

Because Zod performs deep type checking, error messages provide precise feedback (e.g., "name must be at least 1 character long"), eliminating ambiguous validation failures.

Real-World Implementation Patterns

Complex actions benefit from composing shared schemas. This example from the repository patterns demonstrates handling booleanish CLI inputs and structured configurations:

// src/actions/toggle-feature.ts
import { defineAction } from "@agent-native/core/action";
import { z } from "zod";
import { booleanishSchema, idSchema } from "./_schemas";

export default defineAction({
  http: false,
  schema: z.object({
    featureId: idSchema,
    enabled: booleanishSchema, // accepts "true", "yes", 1, etc.
  }),
  async handler({ input }) {
    // enabled is guaranteed to be boolean
    await enableFeature(input.featureId, input.enabled);
    return { featureId: input.featureId, enabled: input.enabled };
  },
});

For creating resources with flexible metadata, combine strict ID validation with optional JSON records:

// src/actions/create-source.ts
import { idSchema, sourceProviderSchema, jsonRecordSchema } from "./_schemas";

export default defineAction({
  http: { method: "POST" }, // exposed as /api/create-source
  schema: z.object({
    id: idSchema,
    provider: sourceProviderSchema,
    config: jsonRecordSchema,
  }),
  async handler({ input, ctx }) {
    await ctx.db.insertSource({
      id: input.id,
      provider: input.provider,
      config: input.config,
    });
    return { success: true };
  },
});

Summary

  • Import defineAction from @agent-native/core/action to declare actions with mandatory Zod schemas
  • Provide a schema property using Zod (z.object, z.enum, etc.) to define input parameters and enable automatic validation
  • Reuse shared schemas from _schemas.ts files (e.g., idSchema, booleanishSchema) to maintain consistency across the codebase
  • Configure HTTP exposure via the http property: use http: false for agent-only actions, http: { method: "GET" } for read endpoints, or default POST for write operations
  • Rely on automatic validation that runs before the handler executes, returning typed input on success or standardized errors on failure

Frequently Asked Questions

What happens if the input fails Zod validation in an Agent-Native action?

If validation fails, the framework intercepts the error before your handler runs. For HTTP requests, it returns a 400 status code with detailed Zod error messages. For LLM tool calls, it returns a structured error object containing the validation failure details. Your handler never receives invalid data.

Can I use the same action for both HTTP API endpoints and LLM tool calls?

Yes. The defineAction function is designed as a single source of truth for both UI calls and LLM-driven tool calls. Configure the http property to expose the action as a REST endpoint while keeping it available for agent invocation, or set http: false to restrict it to agent-only usage.

Where should I define shared Zod schemas for reuse across multiple actions?

Store reusable schemas in a _schemas.ts file within your actions directory, as seen in templates/brain/actions/_schemas.ts. Export common validators like idSchema, sourceProviderSchema, and booleanishSchema, then import them into individual action files to ensure consistent validation patterns across your application.

How do I handle optional or complex JSON input in Agent-Native actions?

Use the jsonRecordSchema from shared schemas or define a custom Zod preprocessor. For optional fields, use z.optional() or provide default values. The jsonRecordSchema utility specifically handles JSON string parsing and defaults to an empty object, making it ideal for flexible configuration objects.

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 →