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

> Learn how to define actions with Standard Schema Zod in Agent-Native. Import defineAction, provide Zod schema, and implement the async handler for typed input and validation.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-06-27

---

**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:

```typescript
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.

```typescript
// 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`](https://github.com/BuilderIO/agent-native/blob/main/_schemas.ts) files to ensure validation consistency across actions. According to the source code in [`templates/brain/actions/_schemas.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/brain/actions/_schemas.ts), common patterns include ID validation, enumerated types, and boolean preprocessing.

Key shared schemas include:

```typescript
// 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`](https://github.com/BuilderIO/agent-native/blob/main/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:

```typescript
// 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:

```typescript
// 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`](https://github.com/BuilderIO/agent-native/blob/main/_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`](https://github.com/BuilderIO/agent-native/blob/main/_schemas.ts) file within your actions directory, as seen in [`templates/brain/actions/_schemas.ts`](https://github.com/BuilderIO/agent-native/blob/main/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.