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
httpor usehttp: { method: "POST" }for default POST behavior - Agent-only: Set
http: falseto 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:
- Incoming request arrives via HTTP, CLI, or LLM tool API
- The runtime extracts the payload and executes the Zod schema validation
- On success: The
handlerreceives a fully typedinputobject and executes business logic - On failure: A
ZodErrortransforms into a standardized error payload—400status 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
defineActionfrom@agent-native/core/actionto declare actions with mandatory Zod schemas - Provide a
schemaproperty using Zod (z.object,z.enum, etc.) to define input parameters and enable automatic validation - Reuse shared schemas from
_schemas.tsfiles (e.g.,idSchema,booleanishSchema) to maintain consistency across the codebase - Configure HTTP exposure via the
httpproperty: usehttp: falsefor 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
inputon 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →