Astro Actions let you define type-safe server functions that you call directly from your frontend components — no REST endpoints, no GraphQL, no boilerplate.
Why Astro Actions?
- Type-safe: Full TypeScript inference from server to client
- Zero boilerplate: Define a function, call it from any component
- Built-in validation: Zod schemas for input validation
- Framework-agnostic: Works with React, Vue, Svelte, Solid inside Astro
- Progressive enhancement: Works with and without JavaScript
Setup
Actions are built into Astro 4.8+. No extra packages needed.
npm create astro@latest my-app
Define Actions
Create src/actions/index.ts:
import { defineAction } from 'astro:actions';
import { z } from 'astro:schema';
export const server = {
createPost: defineAction({
accept: 'json',
input: z.object({
title: z.string().min(1).max(200),
body: z.string().min(10),
tags: z.array(z.string()).optional(),
}),
handler: async (input) => {
const post = await db.post.create({ data: input });
return { id: post.id, slug: post.slug };
},
}),
deletePost: defineAction({
accept: 'json',
input: z.object({ id: z.string() }),
handler: async ({ id }) => {
await db.post.delete({ where: { id } });
return { success: true };
},
}),
};
Call from React Component
import { actions } from 'astro:actions';
export function CreatePostForm() {
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const { data, error } = await actions.createPost({
title: formData.get('title'),
body: formData.get('body'),
});
if (error) console.error(error.message);
else window.location.href = `/posts/${data.slug}`;
}
return (
<form onSubmit={handleSubmit}>
<input name="title" placeholder="Title" />
<textarea name="body" placeholder="Content" />
<button type="submit">Publish</button>
</form>
);
}
Form Actions (Progressive Enhancement)
Works without JavaScript:
---
import { actions } from 'astro:actions';
---
<form method="POST" action={actions.createPost}>
<input name="title" />
<textarea name="body" />
<button type="submit">Create</button>
</form>
Error Handling
const { data, error } = await actions.createPost({ title: '', body: 'test' });
if (error) {
if (error.code === 'INPUT_VALIDATION_ERROR') {
console.log(error.fields); // { title: ['String must contain at least 1 character'] }
}
}
Real-World Use Case
A developer replaced 15 API route files with 8 Astro Actions. Type safety caught 3 bugs during migration that the old REST endpoints had missed for months.
Need to automate data collection for YOUR project? Check out my Apify actors for ready-made scrapers, or email spinov001@gmail.com for custom solutions.
Top comments (0)