{"record":{"id":"b2e535c83bd69a21","repo":"withastro/astro","slug":"expected-handler-for-action-pathkeys-join","errorCode":null,"errorMessage":"Expected handler for action ${pathKeys.join('.')} to be a function. Received ${typeof server}.","messagePattern":"Expected handler for action (.+?) to be a function\\. Received (.+?)\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/core/base-pipeline.ts","lineNumber":401,"sourceCode":"\t\t\t\t});\n\t\t\t}\n\t\t\tif (FORBIDDEN_PATH_KEYS.has(key)) {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...ActionNotFoundError,\n\t\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (!Object.hasOwn(server, key)) {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...ActionNotFoundError,\n\t\t\t\t\tmessage: ActionNotFoundError.message(pathKeys.join('.')),\n\t\t\t\t});\n\t\t\t}\n\t\t\t// @ts-expect-error we are doing a recursion... it's ugly\n\t\t\tserver = server[key];\n\t\t}\n\t\tif (typeof server !== 'function') {\n\t\t\tthrow new TypeError(\n\t\t\t\t`Expected handler for action ${pathKeys.join('.')} to be a function. Received ${typeof server}.`,\n\t\t\t);\n\t\t}\n\t\treturn server;\n\t}\n\n\tasync getModuleForRoute(route: RouteData): Promise<SinglePageBuiltModule> {\n\t\tfor (const defaultRoute of this.defaultRoutes) {\n\t\t\tif (route.component === defaultRoute.component) {\n\t\t\t\treturn {\n\t\t\t\t\tpage: () => Promise.resolve(defaultRoute.instance),\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif (route.type === 'redirect') {\n\t\t\treturn RedirectSinglePageBuiltModule;\n\t\t} else {","sourceCodeStart":383,"sourceCodeEnd":419,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/core/base-pipeline.ts#L383-L419","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before — src/actions/index.ts\nexport const server = {\n  config: { theme: 'dark' }, // not an action\n};\n// calling Astro.callAction('config.theme') → TypeError\n\n// after\nexport const server = {\n  getTheme: defineAction({\n    handler: () => 'dark',\n  }),\n};","handlingStrategy":"type-guard","validationCode":"import { server } from './src/actions/index';\nfunction isActionFunction(server: unknown, path: string): boolean {\n  let current: unknown = server;\n  for (const key of path.split('.')) {\n    if (typeof current !== 'object' || current === null) return false;\n    current = (current as Record<string, unknown>)[key];\n  }\n  return typeof current === 'function';\n}","typeGuard":"function isActionHandler(value: unknown): value is (...args: any[]) => any {\n  return typeof value === 'function';\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["actions","typeerror","handler","export","validation"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}