{"record":{"id":"d9f77facba596eec","repo":"emberjs/ember.js","slug":"you-must-pass-a-function-as-the-fn-helper-s-firs","errorCode":null,"errorMessage":"You must pass a function as the `fn` helper's first argument, you passed ${callbackRef ? valueForRef(callbackRef) : callbackRef}. While rendering:\\n\\n${callbackRef?.debugLabel}","messagePattern":"You must pass a function as the `fn` helper's first argument, you passed (.+?)\\. While rendering:\\\\n\\\\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@glimmer/runtime/lib/helpers/fn.ts","lineNumber":49,"sourceCode":"        } else {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- @fixme\n          return (fn as AnyFn).call(context, ...args, ...invocationArgs);\n        }\n      };\n    },\n    null,\n    'fn'\n  );\n});\n\nfunction assertCallbackIsFn(callbackRef: Reference | undefined): asserts callbackRef is Reference {\n  if (\n    !(\n      callbackRef &&\n      (isInvokableRef(callbackRef) || typeof valueForRef(callbackRef) === 'function')\n    )\n  ) {\n    throw new Error(\n      `You must pass a function as the \\`fn\\` helper's first argument, you passed ${\n        callbackRef ? valueForRef(callbackRef) : callbackRef\n      }. While rendering:\\n\\n${callbackRef?.debugLabel}`\n    );\n  }\n}\n","sourceCodeStart":31,"sourceCodeEnd":56,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/runtime/lib/helpers/fn.ts#L31-L56","documentation":"The `fn` helper turns a function reference into a callback usable in templates. This assertion fires while rendering when the first argument is not an invokable ref wrapping a function — i.e. the first positional arg is missing, null, or resolves to a non-function value. The message includes the passed value and the ref's debug label to locate the offending template expression.","triggerScenarios":"`{{fn this.notAFunction}}` where the property is undefined/null or a non-function; `{{fn}}` with no first argument; passing a string/number/object instead of a function; a typo in the referenced property name.","commonSituations":"Renaming a component action and forgetting the template; a tracked getter returning undefined at render time; passing a bound value from a model that is not a function; classic `action` vs `fn` migration mistakes in Octane upgrades.","solutions":["Pass an actual function: `{{fn this.save}}` or `{{fn (mut this.value)}}` for currying with args: `{{fn this.save arg}}`","Verify the referenced property exists and is a function (check for typos and undefined returns)","If wrapping, use `(perform ...)` or a dedicated helper instead of fn for non-function actions"],"exampleFix":"// before (template)\n<button {{on \"click\" (fn this.handleClicked)}}>...</button> // this.handleClicked is undefined\n// after\n<button {{on \"click\" (fn this.handleClick)}}>...</button> // handleClick: () => {...} defined on the class","handlingStrategy":"type-guard","validationCode":"if (typeof this.handleClick !== 'function') { throw new TypeError('this.handleClick must be a function before use with (fn)'); }","typeGuard":"function isFunctionRef(ref) { return ref && (isInvokableRef(ref) || typeof valueForRef(ref) === 'function'); }","tryCatchPattern":"try { render(template); } catch (e) { if (String(e.message).includes(\"fn helper's first argument\")) { console.error('fn passed a non-function:', e.message); } else { throw e; } }","preventionTips":["Verify referenced properties are functions before using {{fn}}","Watch for getters that may return undefined at render time","Check for typos in action names during renames","Prefer {{fn this.action arg}} for currying, not manual wrapping"],"tags":["glimmer","template-helper","fn","type-error"],"backgroundTag":"fn-helper-not-a-function","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}