Open-Source Wikis

/

Astro

/

Features

/

Actions

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 response

The 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.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Actions – Astro wiki | Factory