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

  1. Call actions in server code through the full path inside Astro.callAction: await Astro.callAction(actions.buy)(input)
  2. When passing an action as a callback, pass a closure: (input) => Astro.callAction(actions.buy)(input)
  3. 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

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


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)