withastro/astro · error · TypeError
Expected handler for action ${pathKeys.join('.')} to be a fu
Error message
Expected handler for action ${pathKeys.join('.')} to be a function. Received ${typeof server}. What it means
A `TypeError` thrown after the full action path is traversed but the final resolved value is not a function. This means the dotted path points to a valid property on the `server` object, but it's a value (object, string, number) rather than an action handler function. The guard `if (typeof server !== 'function')` fires after the loop.
Source
Thrown at packages/astro/src/core/base-pipeline.ts:401
});
}
if (FORBIDDEN_PATH_KEYS.has(key)) {
throw new AstroError({
...ActionNotFoundError,
message: ActionNotFoundError.message(pathKeys.join('.')),
});
}
if (!Object.hasOwn(server, key)) {
throw new AstroError({
...ActionNotFoundError,
message: ActionNotFoundError.message(pathKeys.join('.')),
});
}
// @ts-expect-error we are doing a recursion... it's ugly
server = server[key];
}
if (typeof server !== 'function') {
throw new TypeError(
`Expected handler for action ${pathKeys.join('.')} to be a function. Received ${typeof server}.`,
);
}
return server;
}
async getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {
for (const defaultRoute of this.defaultRoutes) {
if (route.component === defaultRoute.component) {
return {
page: () => Promise.resolve(defaultRoute.instance),
};
}
}
if (route.type === 'redirect') {
return RedirectSinglePageBuiltModule;
} else {View on GitHub (pinned to d081033d5f)
Solutions
- Check that the path in the error points to an actual `defineAction` handler, not a nested object.
- Ensure every leaf in the `server` export is a function (action handler).
- Move non-action utilities out of the `server` export.
- Verify the action path in `Astro.callAction` ends at a handler function.
Example fix
// before — src/actions/index.ts
export const server = {
config: { theme: 'dark' }, // not an action
};
// calling Astro.callAction('config.theme') → TypeError
// after
export const server = {
getTheme: defineAction({
handler: () => 'dark',
}),
}; Defensive patterns
Strategy: type-guard
Validate before calling
import { server } from './src/actions/index';
function isActionFunction(server: unknown, path: string): boolean {
let current: unknown = server;
for (const key of path.split('.')) {
if (typeof current !== 'object' || current === null) return false;
current = (current as Record<string, unknown>)[key];
}
return typeof current === 'function';
} Type guard
function isActionHandler(value: unknown): value is (...args: any[]) => any {
return typeof value === 'function';
} Prevention
- Ensure every leaf in the server export is a defineAction handler.
- Don't place config objects or constants inside the server export.
- Use TypeScript to enforce that server values are action-compatible.
When it happens
Trigger: `getAction('settings.theme')` is called. Traversal succeeds for all keys, but `server['settings']['theme']` is a string or object, not a function. The final `typeof server !== 'function'` check throws a TypeError naming the path and the actual type received.
Common situations: An action path points to a nested configuration object instead of a handler. A non-action property was accidentally added to the `server` export. The `server` object has helper utilities mixed in with actions. A namespace object is referenced as if it were an action.
Related errors
- Expected `server` export in actions file to be an object. Re
- UNSUPPORTED_MEDIA_TYPE
- BAD_REQUEST
- [astro:actions] `defineAction()` unexpectedly used on the cl
- [astro:actions] `getActionContext()` unexpectedly used on th
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/b2e535c83bd69a21.
Report an issue: GitHub.