{"record":{"id":"7c7579f6ee427f7e","repo":"remix-run/remix","slug":"expected-a-request-handler-function-or-action-obje","errorCode":null,"errorMessage":"Expected a request handler function or action object with a function `handler` property","messagePattern":"Expected a request handler function or action object with a function `handler` property","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/fetch-router/src/lib/router.ts","lineNumber":265,"sourceCode":"  return new Response(`Not Found: ${url.pathname}`, { status: 404 })\n}\n\nfunction normalizeMiddleware(\n  middleware: readonly AnyMiddleware[] | undefined,\n): AnyMiddleware[] | undefined {\n  return middleware == null || middleware.length === 0 ? undefined : [...middleware]\n}\n\nfunction normalizeAction(action: unknown): NormalizedAction {\n  if (isRequestHandler(action)) {\n    return {\n      handler: action,\n      middleware: undefined,\n    }\n  }\n\n  if (!isActionObject(action)) {\n    throw new TypeError(\n      'Expected a request handler function or action object with a function `handler` property',\n    )\n  }\n\n  return {\n    handler: action.handler,\n    middleware: normalizeMiddleware(action.middleware),\n  }\n}\n\nfunction mergeMiddleware(\n  upstream: AnyMiddleware[] | undefined,\n  downstream: AnyMiddleware[] | undefined,\n): AnyMiddleware[] | undefined {\n  if (!upstream || upstream.length === 0) {\n    return downstream\n  }\n","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/fetch-router/src/lib/router.ts#L247-L283","documentation":"Thrown by normalizeAction in @remix-run/fetch-router when the value passed as a route action is neither a function nor an object with a function `handler` property. The router accepts handler functions directly or action objects (with optional middleware), and anything else fails this check. It almost always indicates a type mismatch at the call site, e.g. passing a string, undefined, or an object whose handler is not a function.","triggerScenarios":"Calling router.map(), addRoute(), or router.action() with a non-function value: a string path, undefined (e.g. an import that resolved to undefined), an object without a `handler` key, or handler: 'notAFunction'.","commonSituations":"Circular imports making the handler undefined, forgetting to call a factory (passing makeHandler instead of makeHandler()), typoing the `handler` property name, or passing a controller object where a single action is expected.","solutions":["Check the value passed to map/action/addRoute is a function or { handler: fn }","Fix circular imports or missing exports that make the handler undefined","If passing an object, ensure the property is named `handler` and is a function","If you meant to map a controller, pass it to map() with a routes object so mapController runs instead"],"exampleFix":"// before\nrouter.action('/users', UserHandler) // UserHandler is undefined or a string\n\n// after\nimport { UserHandler } from './handlers.ts'\nrouter.action('/users', { handler: UserHandler })","handlingStrategy":"type-guard","validationCode":"let isAction = (v: unknown): boolean =>\n  typeof v === 'function' ||\n  (typeof v === 'object' && v !== null && typeof (v as any).handler === 'function')","typeGuard":"function isActionInput(value: unknown): value is Function | { handler: Function } {\n  return (\n    typeof value === 'function' ||\n    (typeof value === 'object' &&\n      value !== null &&\n      typeof (value as { handler?: unknown }).handler === 'function')\n  )\n}","tryCatchPattern":"catch (error) { if (error instanceof TypeError && /Expected a request handler/.test(error.message)) { /* fix wiring at build time */ } throw error }","preventionTips":["Type route maps as RouteMap and controllers as Controller so TS rejects bad shapes","Run router.map wiring during app startup so misconfiguration fails fast","Avoid circular imports between route modules and handler modules"],"tags":["fetch-router","typescript","validation","route-mapping"],"backgroundTag":"invalid-argument-type","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}