withastro/astro
Actions
Type-safe server-side functions callable from the client (or another server module) without writing API routes by hand. Configured by exporting a server object from src/actions/index.ts.
Purpose
Replace ad-hoc fetch('/api/...') glue with typed call sites that share validation between client and server, support progressive enhancement (form submission → JSON), and integrate with Astro's session/middleware/error pipeline.
Key files
| File | Purpose |
|---|---|
packages/astro/src/actions/integration.ts |
The integration that wires actions into Astro. |
packages/astro/src/actions/runtime/ |
Server runtime (handlers, validation), client runtime (actions proxy), action errors. |
packages/astro/src/actions/runtime/entrypoints/ |
Server and client entrypoints used by the Vite plugin. |
packages/astro/src/actions/vite-plugin-actions.ts |
Vite plugin that emits astro:actions. |
packages/astro/src/actions/utils.ts |
Helpers to bind/serialize action calls. |
packages/astro/src/actions/consts.ts |
Internal constants. |
packages/astro/src/actions/noop-actions.ts |
Stub used when actions aren't enabled. |
Defining an action
// src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
greet: defineAction({
accept: 'json',
input: z.object({ name: z.string().min(1) }),
handler: async ({ name }, context) => `Hello, ${name}!`,
}),
contact: defineAction({
accept: 'form',
input: z.object({ email: z.string().email(), message: z.string() }),
handler: async (input, context) => {
// Send to CRM, etc.
},
}),
};Calling from the client
import { actions } from 'astro:actions';
// JSON action
const greeting = await actions.greet({ name: 'world' });
// → { data: 'Hello, world!', error: undefined } | { data: undefined, error: ActionError }
// Form action
<form method="post" action={actions.contact}>
<input name="email" type="email" />
<textarea name="message" />
<button>Send</button>
</form>Actions return a discriminated union { data, error }. The error is a typed ActionError (subclass of AstroError) with codes like BAD_REQUEST, UNAUTHORIZED, INTERNAL_SERVER_ERROR, FORBIDDEN.
How it works
sequenceDiagram
participant Client
participant Vite as vite-plugin-actions
participant ActionsHandler as /_actions/<name>
participant Server as user defineAction handler
Vite->>Client: Generate astro:actions proxy
Client->>Client: actions.greet({ name: 'world' })
Client->>ActionsHandler: POST /_actions/greet
ActionsHandler->>ActionsHandler: Validate Zod input
ActionsHandler->>Server: handler(input, context)
Server-->>ActionsHandler: result or thrown error
ActionsHandler-->>Client: { data } or { error }
Client-->>Client: typed responseThe handler receives the same RenderContext-derived context as a page, so context.locals, context.session, context.cookies are all available. Encryption uses the same project key as sessions and server islands (set via astro create-key).
Form actions and progressive enhancement
When accept: 'form', the action endpoint accepts both multipart/form-data and the astro:actions JSON envelope. A static page with a <form action={actions.contact}> works without JavaScript — the user submits the form, the server processes it, and the page renders the result. The same action with the JS proxy gives an inline UX.
Related pages
- features / sessions
- systems / render context
- systems / CLI —
astro create-key. - systems / error handling —
ActionErroris a sibling ofAstroError.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.