emberjs/ember.js · error · Error
You must pass a function as the `fn` helper's first argument
Error message
You must pass a function as the `fn` helper's first argument, you passed ${callbackRef ? valueForRef(callbackRef) : callbackRef}. While rendering:\n\n${callbackRef?.debugLabel} What it means
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.
Source
Thrown at packages/@glimmer/runtime/lib/helpers/fn.ts:49
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return -- @fixme
return (fn as AnyFn).call(context, ...args, ...invocationArgs);
}
};
},
null,
'fn'
);
});
function assertCallbackIsFn(callbackRef: Reference | undefined): asserts callbackRef is Reference {
if (
!(
callbackRef &&
(isInvokableRef(callbackRef) || typeof valueForRef(callbackRef) === 'function')
)
) {
throw new Error(
`You must pass a function as the \`fn\` helper's first argument, you passed ${
callbackRef ? valueForRef(callbackRef) : callbackRef
}. While rendering:\n\n${callbackRef?.debugLabel}`
);
}
}
View on GitHub (pinned to 26f97246a8)
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
Example fix
// before (template)
<button {{on "click" (fn this.handleClicked)}}>...</button> // this.handleClicked is undefined
// after
<button {{on "click" (fn this.handleClick)}}>...</button> // handleClick: () => {...} defined on the class Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof this.handleClick !== 'function') { throw new TypeError('this.handleClick must be a function before use with (fn)'); } Type guard
function isFunctionRef(ref) { return ref && (isInvokableRef(ref) || typeof valueForRef(ref) === 'function'); } Try / catch
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; } } Prevention
- 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
When it happens
Trigger: `{{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.
Common situations: 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.
Related errors
- `eq` expects exactly two arguments, but received ${arguments
- `gt` expects exactly two arguments, but received ${arguments
- `gte` expects exactly two arguments, but received ${argument
- Expected a context object to be passed as the first paramete
- Compile Error: ${template.problem} @ ${template.span.start}.
AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01).
Data as JSON: /api/errors/d9f77facba596eec.
Report an issue: GitHub.