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

The server-side actions proxy requires per-request state (the SSR manifest and current context) which Astro attaches via an internal fetch-state symbol during request handling. When an action is invoked in server code outside that flow — directly in a page or endpoint body — the state is absent, so Astro throws ActionCalledFromServerError telling you to route the call through Astro.callAction().

Solutions

  1. Wrap server-side calls: const result = await Astro.callAction(actions.myAction)(input)
  2. If you need the logic outside a request, extract a plain function and have both the action handler and the caller use it
  3. Make sure the file executing the call receives the Astro context (a page/endpoint), not a detached module

Example fix

// before — src/pages/dashboard.astro frontmatter
const result = await actions.getUser(userId); // throws

// after
const result = await Astro.callAction(actions.getUser)(userId);
Defensive patterns

Strategy: validation

Validate before calling

// normalize server-side calls through one helper so context is never missing
export async function callFromServer<A extends (input: never) => Promise<unknown>>(
  Astro: AstroGlobal,
  action: A,
  input: Parameters<A>[0],
) {
  if (!Astro?.callAction) throw new Error('Not in an Astro request scope');
  return Astro.callAction(action)(input);
}

Type guard

function hasCallAction(astro: unknown): astro is AstroGlobal {
  return typeof astro === 'object' && astro !== null && typeof (astro as AstroGlobal).callAction === 'function';
}

Try / catch

try {
  const r = await actions.getUser(id);
} catch (e) {
  if (e instanceof AstroError && e.code === 'ActionCalledFromServerError') {
    // re-route through: await Astro.callAction(actions.getUser)(id)
  } else throw e;
}

Prevention

When it happens

Trigger: Calling actions.myAction(input) directly in .astro frontmatter or in an API endpoint without the wrapper; invoking the server actions proxy from code that runs outside a request (module scope, background job) where no Astro context exists.

Common situations: Porting client-style action calls into a page; calling actions during prerender/module evaluation; calling an action inside an endpoint to reuse logic instead of extracting the handler.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/75dc6c2ed1f4be54. Report an issue: GitHub.

Appendix: source

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

	ActionInputSchema,
	ActionReturnType,
	SafeResult,
} from '../types.js';

export const getActionPath = createGetActionPath({
	baseUrl: import.meta.env.BASE_URL,
	shouldAppendTrailingSlash,
});

export const actions = createActionsProxy({
	handleAction: async (param, path, context) => {
		const state: FetchState | undefined = context
			? Reflect.get(context, fetchStateSymbol)
			: undefined;
		if (!state) {
			// The context was not created by Astro's request handling (e.g.
			// an action invoked from server code without a context).
			throw new AstroError(ActionCalledFromServerError);
		}
		const action = await getAction(state.manifest, path);
		if (!action) throw new Error(`Action not found: ${path}`);
		return action.bind(context)(param);
	},
});

View on GitHub (pinned to 52e6c34790)