withastro/astro · error · AstroError
ActionCalledFromServerError
ActionCalledFromServerError
Error message
Action called from a server-rendered page or endpoint without using `Astro.callAction()`. This wrapper must be used to call actions from server code.
What it means
Every server action runs its handler with `this` bound to an ActionAPIContext (marked internally by ACTION_API_CONTEXT_SYMBOL and containing params/cookies/locals). The runtime verifies that binding before executing; if the function is invoked with a wrong or missing `this` — typically a detached reference called as a bare function in server code — the check fails and ActionCalledFromServerError is thrown.
Solutions
- Call actions in server code through the full path inside Astro.callAction: await Astro.callAction(actions.buy)(input)
- When passing an action as a callback, pass a closure: (input) => Astro.callAction(actions.buy)(input)
- Never import the handler directly from src/actions internals — always go through the actions proxy
Example fix
// before — server code
const { buy } = actions;
await buy(input); // lost receiver -> throws
// after
await Astro.callAction(actions.buy)(input); Defensive patterns
Strategy: validation
Validate before calling
// keep the receiver attached when actions must be passed around const runBuy = (input: BuyInput) => Astro.callAction(actions.buy)(input); // later: await runBuy(input) — closure preserves context binding
Try / catch
try {
await buyRef(input); // a detached action reference
} catch (e) {
if (e instanceof AstroError && e.code === 'ActionCalledFromServerError') {
// replace with Astro.callAction(actions.buy)(input)
} else throw e;
} Prevention
- Never destructure actions out of the actions object in server code
- Pass closures that call actions through Astro.callAction, not raw handler references
- Centralize server-side action calls in one helper module to enforce the pattern
When it happens
Trigger: Destructuring or aliasing an action then calling it as a plain function in server code: `const buy = actions.buy; await buy(input)`. Calling the raw handler exported from the actions module directly instead of through the proxy / Astro.callAction.
Common situations: Passing actions.myAction as a callback (e.g. to Promise.all or an event pipeline) which drops the receiver; refactoring imports to pull individual handlers out of the actions object; mixing client-call style into server code.
Related errors
- ActionCalledFromServerError
- [astro:actions] `defineAction()` unexpectedly used on the…
- [astro:actions] `getActionContext()` unexpectedly used on…
- A collision will result in a hard error in following…
- ActionNotFoundError
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/157518ab28c2351d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/actions/runtime/server.ts:58
: undefined,
>({
accept,
input: inputSchema,
handler,
}: {
input?: TInputSchema;
accept?: TAccept;
handler: ActionHandler<TInputSchema, TOutput>;
}): ActionClient<TOutput, TAccept, TInputSchema> & string {
const serverHandler =
accept === 'form'
? getFormServerHandler(handler, inputSchema)
: getJsonServerHandler(handler, inputSchema);
async function safeServerHandler(this: ActionAPIContext, unparsedInput: unknown) {
// The ActionAPIContext should always contain the `params` property
if (typeof this === 'function' || !isActionAPIContext(this)) {
throw new AstroError(ActionCalledFromServerError);
}
return callSafely(() => serverHandler(unparsedInput, this));
}
Object.assign(safeServerHandler, {
orThrow(this: ActionAPIContext, unparsedInput: unknown) {
if (typeof this === 'function') {
throw new AstroError(ActionCalledFromServerError);
}
return serverHandler(unparsedInput, this);
},
});
return safeServerHandler as ActionClient<TOutput, TAccept, TInputSchema> & string;
}
function getFormServerHandler<TOutput, TInputSchema extends z.$ZodType>(
handler: ActionHandler<TInputSchema, TOutput>,View on GitHub (pinned to 52e6c34790)