withastro/astro · error · Error
FetchState not found on APIContext. This is an internal erro
Error message
FetchState not found on APIContext. This is an internal error — the context was not created through Astro's request pipeline.
What it means
Astro's request pipeline stashes a per-request FetchState on the APIContext under a module-private symbol (fetchStateSymbol). getFetchStateFromAPIContext() reads that symbol and throws this error when it is absent, meaning the context object was never created through Astro's request pipeline (FetchState.getAPIContext / app.render). It is an internal invariant violation, not a configuration problem: either someone hand-built an APIContext, or two copies of Astro core are running and the symbol from one copy is not found by the other.
Source
Thrown at packages/astro/src/core/fetch/fetch-state.ts:139
status: number;
/**
* Triggers a rewrite to a different route.
*
* [Astro reference](https://docs.astro.build/en/guides/routing/#rewrites)
*/
rewrite(payload: RewritePayload): Promise<Response>;
}
/**
* Retrieves the `FetchState` stashed on an `APIContext` by
* `FetchState.getAPIContext()`. Throws if not found — this indicates
* the context was not created through Astro's request pipeline.
*/
export function getFetchStateFromAPIContext(context: APIContext): FetchState {
const state = (context as any)[fetchStateSymbol] as FetchState | undefined;
if (!state) {
throw new Error(
"FetchState not found on APIContext. This is an internal error — the context was not created through Astro's request pipeline.",
);
}
return state;
}
/**
* Holds per-request state as it flows through the handler pipeline.
*
* **This class is user-facing** via `astro/fetch` and `astro/hono`.
* The {@link AstroFetchState} interface defines the stable public
* surface. Members not on that interface are internal and
* may change without notice.
*
* Performance note: fields on this class are plain properties — ES
* private fields (`#foo`) have a non-zero per-access cost in V8
* which is measurable on the hot render path, so `#` is used only
* for rarely-accessed memoized caches and Maps.View on GitHub (pinned to e294953aa8)
Solutions
- Route requests through Astro's own pipeline (app.render / the built server entry) instead of passing a hand-made context into Astro internals
- If you write an adapter, delegate rendering to Astro's handler so contexts are stamped by FetchState, never construct APIContext yourself
- In tests, use the built server entry or Astro's official test utilities rather than fabricating contexts
- Check for duplicate astro copies (pnpm why astro / npm ls astro) and dedupe with version overrides so the symbol module is loaded once
- If every party involved is first-party Astro code, report it as an Astro bug with a reproduction
Example fix
// before (custom adapter)
const context = { request, params: {}, locals: {} } as APIContext;
await someAstroInternal(context);
// after — let Astro create the context via its pipeline
const response = await app.render(request); Defensive patterns
Strategy: try-catch
Try / catch
try {
// call the Astro internal that needs the pipeline context
} catch (err) {
if (err instanceof Error && err.message.includes('FetchState not found on APIContext')) {
throw new Error('Context was not created by the Astro request pipeline — pass requests through app.render()', { cause: err });
}
throw err;
} Prevention
- Never hand-construct an APIContext; let Astro's pipeline create it via app.render or the built server entry
- Adapters and integrations should delegate to Astro's handler instead of synthesizing contexts
- Dedupe astro in the lockfile (pnpm overrides / resolutions) so the symbol module loads once
- In tests, drive the built server entry rather than fabricating context objects
When it happens
Trigger: An internal helper that expects a pipeline-created context receives one built by hand or by a third party: a custom adapter calling Astro internals with its own fabricated context object, a unit test constructing an APIContext literal, or duplicate astro package instances in node_modules (e.g. pnpm resolving two versions) so fetchStateSymbol differs between the stamper and the reader.
Common situations: Writing a custom adapter or integration that builds its own context instead of delegating to Astro's handler; unit tests that fake an APIContext; monorepos where several @astrojs/* integrations pin different astro versions, loading astro core twice; mixing a locally linked astro with a registry-installed one.
Related errors
- Unexpectedly unable to find a component instance for route $
- Astro couldn't find the correct page to render, probably bec
- Unexpectedly unable to find a component instance for route $
- Astro couldn't find the correct page to render, probably bec
- Unable to resolve [${specifier}]
AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18).
Data as JSON: /api/errors/f921ca793bb24883.
Report an issue: GitHub.