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 therunfunction.outputSchema(optional) – A Zod schema validating the returned data. If the action returns invalid data, the framework responds based on theoutputErrorStrategysetting.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:
- The request body is parsed and validated against the input Zod schema.
- If validation passes, the
runfunction executes with fully typed arguments. - The returned object
{ folderId }is validated againstoutputSchema. - Any output violation triggers the configured
outputErrorStrategy(defaulting towarn).
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 withfalse.readOnly– Automatically inferred from the HTTP method when not explicitly set, affecting UI permission hints.toolCallableandagentTool– 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
defineActioninpackages/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
schemaproperty to enable automatic input validation before yourrunfunction executes. - Use
outputSchemawithoutputErrorStrategyto validate return values and handle mismatches via strict errors, warnings, or fallback values. - The framework uses
wrapWithValidationandwrapWithOutputValidationto inject Zod'ssafeParselogic, 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →