komal.nakrani
Browse Themes
ThemesCategoriesDocsBlogTips & Tricks
Server Rendering••By Komal Nakrani

Fix: Action Called From the Server Without Astro.callAction()

Fix 'action called from the server without Astro.callAction()': use Astro.callAction(actions.name, input), or move shared logic into a plain function.

#Astro#Errors#Actions

Astro shows “Action called from the server without Astro.callAction()” when a page, component or endpoint calls an action like a normal function, for example await actions.subscribe(data) in frontmatter. The message: “This wrapper must be used to call actions from server code.” Actions are designed to be called from the browser or from forms. On the server, call them through Astro.callAction() (or context.callAction() in endpoints and middleware), or skip the action and call the underlying function directly.

Key Takeaways

  • In .astro frontmatter: await Astro.callAction(actions.name, input).
  • In endpoints and middleware: context.callAction(actions.name, input).
  • Results come back as { data, error }, same as from the browser.
  • If server code calls it often, move the logic into a plain function both can use.

Wrong

---
export const prerender = false;
import { actions } from 'astro:actions';

const result = await actions.getRecommendations({ userId: '42' });
---
---
export const prerender = false;
import { actions } from 'astro:actions';

const { data, error } = await Astro.callAction(actions.getRecommendations, { userId: '42' });
---
{error ? <p>Couldn't load recommendations.</p> : <ul>{data.map((r) => <li>{r.title}</li>)}</ul>}

Often better: a shared function

Actions add validation and serialisation, which is useful at a network boundary but redundant between two pieces of server code. Keep the logic in a module and call it from both places:

// src/lib/recommendations.ts
export async function getRecommendations(userId: string) {
  // query database or API
  return [{ title: 'Astro routing guide' }];
}
// src/actions/index.ts
import { defineAction } from 'astro:actions';
import { z } from 'astro/zod';
import { getRecommendations } from '../lib/recommendations';

export const server = {
  getRecommendations: defineAction({
    input: z.object({ userId: z.string() }),
    handler: ({ userId }) => getRecommendations(userId),
  }),
};
---
import { getRecommendations } from '../lib/recommendations';
const items = await getRecommendations(Astro.locals.userId);
---

The page calls the function; the browser calls the action. Both share one implementation.

Form results are different

To show the result of a form that posted to an action, use Astro.getActionResult(actions.name), not callAction(). That reads the result of the request that just happened.

Related: Actions must be used with server output. The Astro SSR guide shows a login action end to end, and the newsletter signup tip calls one from a form. See the callAction() reference.

FAQ

Does Astro.callAction() run the input validation?

Yes. The Zod input schema still applies, and errors come back in error.

Can I call an action from a static page’s frontmatter?

No. Server calls during a static build have no request context. Use a plain function at build time instead.