Actions

Typed Server Functions Called From Anywhere

Actions

Astro Actions are typed server functions that you can call from pages, components, islands, or plain `<form>`s — without hand-writing endpoints.

4 min read Level 2/5 #astro#actions#api
What you'll learn
  • Define an action
  • Call it from a form
  • Call it from a client island

Astro Actions are a higher-level alternative to hand-rolling endpoints. You write a typed function on the server; you call it from anywhere — a <form>, an island, an Astro page.

Actions require server mode (or hybrid with the page non-prerendered).

Define an Action

// src/actions/index.ts
import { defineAction } from "astro:actions";
import { z } from "astro:schema";

export const server = {
  saveContact: defineAction({
    accept: "form",            // accept FormData submissions
    input: z.object({
      email: z.string().email(),
      message: z.string().min(1),
    }),
    handler: async ({ email, message }) => {
      await db.contacts.insert({ email, message });
      return { ok: true };
    },
  }),
};

Astro generates types so callers know what saveContact accepts and what it returns.

Call From a Form

---
import { actions } from "astro:actions";
---

<form method="POST" action={actions.saveContact}>
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>

The action={actions.saveContact} attribute points the form at the right action. Astro handles validation, runs your handler, and exposes the result via Astro.getActionResult() on the page.

Call From a Client Island

// In a React island
import { actions } from "astro:actions";

export default function NewsletterForm() {
  async function onSubmit(e) {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const { data, error } = await actions.saveContact(formData);
    if (error) console.error(error);
    else alert("Thanks!");
  }
  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <button>Subscribe</button>
    </form>
  );
}

The same typed action, called from a fetch-like API on the client.

Why Use Actions vs Endpoints?

  • Type safety end-to-end — Zod input + typed return value
  • Less boilerplate — no Response.json plumbing
  • Form-and-client friendly — one definition handles both
  • Built-in validation — Astro returns errors automatically

For simple use cases or when you need raw control over the Response, plain endpoints are still fine.

Up Next

Code that runs before every request — middleware.

Middleware →