# How to Use Astro Actions for Server-Side Data Mutations and API Endpoints

> Learn how to use Astro Actions for secure server-side data mutations and API endpoints. This guide explains type-safe functions and client-side invocation for robust web applications.

- Repository: [Astro/astro](https://github.com/withastro/astro)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Astro Actions provide type-safe server-only functions that enable secure data mutations and API endpoints through a virtual module system, allowing client-side invocation via RPC calls or HTML forms without exposing server logic to the browser.**

Astro Actions, available in the withastro/astro framework, solve the challenge of securely handling server-side mutations in modern web applications. This system lets you define type-safe server functions that can be invoked from the client or used internally within API routes and middleware, ensuring sensitive operations never execute in the browser.

## How Astro Actions Work Under the Hood

Astro Actions leverage a **virtual module system** (`astro:actions`) that bridges client and server through a type-safe RPC layer. The implementation spans several key files in the Astro core:

- **[`src/actions/runtime/server.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/server.ts)** – Exports `defineAction` and handles request parsing, validation, and execution in an isolated server context.
- **[`src/actions/runtime/client.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/client.ts)** – Provides the client-side `actions` proxy that forwards calls to the RPC endpoint.
- **[`src/actions/vite-plugin-actions.ts`](https://github.com/withastro/astro/blob/main/src/actions/vite-plugin-actions.ts)** – The Vite plugin that generates virtual modules, watches the `src/actions` directory, and bundles user-defined actions into a single entry point.
- **[`src/actions/runtime/entrypoints/server.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/entrypoints/server.ts)** – Exports server-side helpers including `getActionContext` for middleware integration.

When a client invokes `actions.addToCart()`, the **client proxy** creates a `POST` request to `/_actions/addToCart`. The **Vite actions plugin** ensures the server has access to the bundled actions code. On the server, `parseRequestBody` processes the JSON or `FormData` payload, validates it against the Zod schema defined in `defineAction`, and executes the handler within a **safe server handler** (`safeServerHandler`) that runs in an isolated `ActionAPIContext`. Results are serialized using **devalue** and returned to the client as a `SafeResult` object containing either `data` or `error`.

## Defining Type-Safe Server Actions

To create a mutation endpoint, use `defineAction` exported from `astro:actions`. This function registers your handler at build time and enforces runtime input validation through the `input` property.

```ts
// src/actions/index.ts
import { defineAction, ActionError } from 'astro:actions';
import { z } from 'zod';

export const addToCart = defineAction({
  input: z.object({
    productId: z.string(),
    quantity: z.number().int().min(1),
  }),
  async handler({ productId, quantity }) {
    const result = await db.cart.add(productId, quantity);
    if (!result) throw new ActionError({ code: 'INTERNAL_SERVER_ERROR' });
    return { success: true };
  },
});

```

The `input` property accepts a Zod schema that automatically validates incoming requests. If validation fails, Astro returns a typed error to the client before your handler executes. The `handler` function runs exclusively on the server, allowing you to safely use database clients, environment variables, or file system APIs without exposing them to the browser.

## Invoking Actions from Client Components

On the client, Astro generates a type-safe proxy that mirrors your server definitions. Import `actions` from `astro:actions` to access your defined mutations with full TypeScript support.

```ts
// src/components/AddToCartButton.jsx
import { actions } from 'astro:actions';

async function onClick() {
  const { data, error } = await actions.addToCart({
    productId: 'abc123',
    quantity: 2,
  });

  if (error) console.error(error);
  else console.log('Added to cart', data);
}

```

The client proxy, implemented in [`src/actions/runtime/client.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/client.ts), automatically constructs the correct `POST` request to `/_actions/addToCart` and handles the devalue-serialized response. The returned `SafeResult` type forces you to handle potential errors before accessing the `data` property, ensuring robust error handling.

## Handling Actions via HTML Forms

Astro Actions support progressive enhancement through standard HTML forms. By posting directly to the RPC route, you can trigger mutations without any client-side JavaScript.

```html
<form method="POST" action="/_actions/addToCart">
  <input type="hidden" name="_action" value="addToCart" />
  <input name="productId" value="abc123" />
  <input name="quantity" type="number" value="1" />
  <button type="submit">Add to cart</button>
</form>

```

When the form submits, Astro's server-side logic in [`src/actions/runtime/server.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/server.ts) invokes `parseRequestBody` and `formDataToObject` to convert the `FormData` into the structured object expected by your Zod schema. This approach provides zero-JavaScript functionality while maintaining the same type-safe validation as RPC calls.

## Server-to-Server Action Patterns

Within API routes or middleware, you can invoke actions directly without HTTP overhead using the server-side `actions` proxy, or inspect the current action context using `getActionContext`.

```ts
// src/pages/api/checkout.ts
import { actions, getActionContext } from 'astro:actions';
import type { APIContext } from 'astro';

export async function POST({ request }: APIContext) {
  const { action } = getActionContext({ request, locals: {} });
  
  const result = await actions.addToCart({ 
    productId: 'xyz', 
    quantity: 3 
  });
  
  return new Response(JSON.stringify(result));
}

```

The `getActionContext` helper, exported from [`src/actions/runtime/server.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/server.ts), extracts metadata about the current action request, including the action name and source. This enables middleware to intercept action results, modify headers, or implement custom logging across all mutations.

## Summary

- **Astro Actions** are defined using `defineAction` in [`src/actions/index.ts`](https://github.com/withastro/astro/blob/main/src/actions/index.ts) and provide type-safe server-only mutation handlers.
- The system uses a **virtual module** (`astro:actions`) and Vite plugin to generate type-safe client proxies and server entry points.
- **Client invocation** occurs via the `actions` proxy, which sends `POST` requests to `/_actions/[action-name]` and returns `SafeResult` objects.
- **HTML forms** can post directly to `/_actions/` endpoints for zero-JavaScript functionality, with automatic `FormData` parsing via `parseRequestBody`.
- **Server-side usage** avoids HTTP round-trips by importing `actions` directly in API routes, while `getActionContext` enables middleware inspection of action metadata.

## Frequently Asked Questions

### How do Astro Actions differ from standard API routes?

Standard API routes require manual request parsing, validation, and response serialization. Astro Actions abstract this through `defineAction`, which automatically validates inputs against Zod schemas, handles errors via `ActionError`, and serializes responses using devalue, providing end-to-end type safety without boilerplate.

### Can Astro Actions work without client-side JavaScript?

Yes. By posting HTML forms directly to the `/_actions/[name]` endpoint, you can trigger mutations without JavaScript. Astro parses the `FormData` automatically in [`src/actions/runtime/server.ts`](https://github.com/withastro/astro/blob/main/src/actions/runtime/server.ts) and validates it against your action's schema, supporting progressive enhancement patterns.

### How does type safety work between the client and server?

The `astro:actions` virtual module generates TypeScript definitions based on your server-side `defineAction` exports. When you import `actions` on the client, TypeScript knows the exact parameter types and return shapes, ensuring compile-time safety that matches runtime validation.

### What validation libraries are supported by Astro Actions?

Currently, Astro Actions integrates with **Zod** through the `input` property of `defineAction`. The system validates incoming requests against your Zod schema before executing the handler, throwing typed validation errors that the client proxy captures in the `error` property of the result object.