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

  1. Pass an actual function: `{{fn this.save}}` or `{{fn (mut this.value)}}` for currying with args: `{{fn this.save arg}}`
  2. Verify the referenced property exists and is a function (check for typos and undefined returns)
  3. 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

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


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/d9f77facba596eec. Report an issue: GitHub.