How to Define a New Action in Agent-Native Using `defineAction`

To define a new action in Agent-Native, import defineAction from @agent-native/core, provide a Zod schema for input validation, implement an async run function containing your business logic, and export the result as the default export.

In the BuilderIO/agent-native framework, every piece of business logic is encapsulated as a shared action. This architecture ensures that whether you are building a UI component, an agent tool, or a CLI command, you invoke the same canonical implementation. Defining a new action requires only a single file that registers itself with the framework at build time.

Architecture Overview

Agent-Native treats actions as the central source of truth for all work. When you use defineAction, you create a contract that specifies exactly what data the action expects and how it executes.

  • Schema Validation: You supply a Zod schema (schema: z.object({...})) that guarantees the shape of input arguments. The framework validates the payload before the run handler executes.
  • Run Implementation: The run function receives the parsed arguments and an execution context containing helpers like database connections or auth utilities.
  • Client Usage: All clients call the same exported action via useActionMutation or useActionQuery (frontend) or appAction (backend). No separate REST endpoint is required.
  • Observability: The core runtime in packages/core/src/action.ts automatically wraps every action with telemetry, permission checks, and error handling.

Creating Your First Action

Importing the Core Utility

Begin by importing defineAction from the core package. This utility is the primary entry point for registering actions with the Nitro server and client-side registry.

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

Defining the Input Schema

Provide a Zod schema that describes the expected input fields. This schema acts as a runtime validator, preventing malformed data from reaching your business logic.

const schema = z.object({
  userId: z.string(),
  userEmail: z.string().email(),
  welcomeMessage: z.string().optional(),
});

Implementing the Run Handler

Write the run function to execute the actual work, such as database writes or external API calls. This function receives the validated arguments and the execution context.

run: async ({ userId, userEmail, welcomeMessage }) => {
  await db.insert(welcomeEmails).values({
    userId,
    email: userEmail,
    message: welcomeMessage ?? "Welcome!",
  });
  
  return { success: true };
}

Exporting for Registration

Export the result of defineAction as the default export so the framework can discover it automatically. The action is registered at build time and published to the shared action registry.

Complete Code Example

Here is a minimal skeleton that demonstrates the full pattern used across the repository:

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

export default defineAction({
  description: "Create a welcome email for a new user",

  schema: z.object({
    userId: z.string(),
    userEmail: z.string().email(),
    welcomeMessage: z.string().optional(),
  }),

  run: async ({ userId, userEmail, welcomeMessage }) => {
    await db.insert(welcomeEmails).values({
      userId,
      email: userEmail,
      message: welcomeMessage ?? "Welcome!",
    });

    return { success: true };
  },
});

Real-world reference: See the create-workflow action in packages/scheduling/src/actions/create-workflow.ts for a complex implementation involving database transactions and conditional logic.

Consuming the Action

Once defined, the action becomes instantly available throughout the entire application under an auto-generated name based on the file path.

From React Components

Use the useActionMutation hook from @agent-native/react to invoke the action from the frontend. The framework handles loading states, caching, and error boundaries automatically.

import { useActionMutation } from "@agent-native/react";

const sendWelcome = useActionMutation("welcomeEmail.create");

function SendButton() {
  const { mutate, isLoading } = sendWelcome;

  const handleClick = () => {
    mutate({
      userId: "123",
      userEmail: "joe@example.com",
      welcomeMessage: "Hey Joe, glad you're here!",
    });
  };

  return <button onClick={handleClick} disabled={isLoading}>Send</button>;
}

From Agent Tools or API Routes

On the server side, use the appAction helper to invoke the action from agent tools, API routes, or the CLI.

import { appAction } from "@agent-native/core";

await appAction("welcomeEmail.create", {
  userId: ctx.user.id,
  userEmail: ctx.user.email,
});

All calls route through the same run implementation, guaranteeing identical behavior and validation rules across every layer of the stack.

Key Source Files and References

Summary

  • Import defineAction from @agent-native/core to create a new action.
  • Define a Zod schema to validate inputs before execution.
  • Implement an async run function containing your business logic.
  • Export the result as the default export to register it with the framework.
  • Invoke the action from any client using useActionMutation (frontend) or appAction (backend).

Frequently Asked Questions

What is the purpose of the Zod schema in defineAction?

The Zod schema acts as a runtime contract that validates the shape of incoming arguments. If the payload fails validation, the framework rejects the request before your run function executes, ensuring type safety and preventing malformed data from reaching your database or external APIs.

Can I access the database or authentication context inside the run function?

Yes. The run function receives a context object as its second argument containing execution helpers like database connections, authentication state, and other framework utilities. This allows you to perform secure operations without manual context passing.

How does the framework generate the action name?

The action name is auto-generated based on the file path relative to the actions directory. For example, a file at actions/welcomeEmail/create.ts becomes accessible as "welcomeEmail.create" in useActionMutation and appAction calls.

Where should I place new action files in the project?

Place new action files inside the actions/ directory of the relevant package or template. The build system scans these directories, automatically imports files that export a defineAction result as default, and registers them with the shared action surface.

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 →