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

Inside defineAction(), the wrapped safeServerHandler verifies its this binding is a genuine ActionAPIContext (carrying ACTION_API_CONTEXT_SYMBOL). If this is a plain function or lacks the symbol, AstroError ActionCalledFromServerError is thrown. This guards the handler from being invoked as a bare reference instead of through Astro.callAction().

Source

Thrown at packages/astro/src/actions/runtime/server.ts:57

		: 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 d081033d5f)

Solutions

  1. Always call via Astro.callAction(actions.myAction, input) on the server.
  2. Do not invoke the action reference directly; do not pass it as a detached callback.
  3. If you need the logic elsewhere, extract a plain function and call that from both the handler and the other site.

Example fix

// before
import { actions } from './actions';
const { myAction } = actions;
const result = await myAction(input);
// after
const result = await Astro.callAction(actions.myAction, input);
Defensive patterns

Strategy: validation

Validate before calling

// Disallow calling action references directly; route all server calls through Astro.callAction.
// Static lint rule (conceptual): flag any `await someAction(` that is not preceded by Astro.callAction.
// Example runtime helper used in server code:
async function serverCall<T>(astro: any, fn: Function, input: unknown): Promise<T> {
  if (typeof astro?.callAction !== 'function') throw new Error('Use Astro.callAction on the server');
  return astro.callAction(fn, input);
}

Prevention

When it happens

Trigger: Destructuring an action and calling it directly (await myAction(input)) on the server, or passing the action reference as a callback and then invoking it, so this is undefined/not the Astro context.

Common situations: Server code that treats the action like an ordinary async function; passing actions.foo into a utility that calls it; calling from a non-Astro module where no context is bound.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/157518ab28c2351d. Report an issue: GitHub.