can1357/oh-my-pi · error

Missing helper: "${call.name}"

Error message

Missing helper: "${call.name}"

What it means

In evaluateCall(), when a mustache call resolves to neither a registered helper nor the built-in `lookup`, and it was invoked with arguments (or forceHelper is set), the engine throws `Missing helper: "<name>"`. Handlebars treats a parenthesized call as a helper invocation, so an unknown helper name is fatal rather than falling back to path resolution.

Source

Thrown at packages/utils/src/template.ts:432

}

function evaluateCall(
	call: CallExpression,
	frame: Frame,
	evaluation: Evaluation,
	forceHelper: boolean,
	body: Node[] = [],
	inverse: Node[] = [],
): unknown {
	const helper = findHelper(call.name, evaluation);
	const args = call.args.map(argument => evaluateExpression(argument, frame, evaluation));
	const hash = evaluateHash(call.hash, frame, evaluation);
	if (helper)
		return helper.call(frame.context, ...args, helperOptions(call.name, hash, frame, evaluation, body, inverse));
	// Handlebars built-in: `{{lookup obj key}}` → proto-safe `obj[key]`.
	// Resolved after user helpers so a registered `lookup` override wins.
	if (call.name === "lookup" && args.length >= 2) return property(args[0], String(args[1]));
	if (forceHelper || args.length) throw new Error(`Missing helper: "${call.name}"`);
	for (const _key in hash) throw new Error(`Missing helper: "${call.name}"`);
	return resolvePath(call.name, frame);
}

function evaluateBlock(node: BlockNode, frame: Frame, evaluation: Evaluation): string {
	const { name } = node.expression;
	const helper = findHelper(name, evaluation);
	if (helper) return stringify(evaluateCall(node.expression, frame, evaluation, true, node.body, node.inverse), false);
	const args = node.expression.args.map(argument => evaluateExpression(argument, frame, evaluation));
	const value = args.length ? args[0] : resolvePath(name, frame);
	const hash = evaluateHash(node.expression.hash, frame, evaluation);
	if (name === "if" || name === "unless") {
		const truthy = isConditionalTruthy(value, hash.includeZero === true);
		const branch = name === "if" ? truthy : !truthy;
		return renderNodes(branch ? node.body : node.inverse, frame, evaluation);
	}
	if (name === "each") {
		if (Array.isArray(value)) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Register the helper: pass it in the helpers map (or runtime.helpers) when invoking the template engine.
  2. Fix the typo / update the template to a helper that exists.
  3. If the name is really data (a path lookup), remove the arguments so it falls back to `resolvePath` — e.g. `{{name}}` instead of `{{name arg}}`.

Example fix

// before
render(src, data); // template uses {{upcase name}}
// after
render(src, data, { helpers: { upcase: (s) => String(s).toUpperCase() } });
Defensive patterns

Strategy: validation

Validate before calling

function assertHelpersRegistered(src, helpers = {}) {
  const called = new Set();
  for (const t of src.match(/\{\{[^}]*\}\}/g) ?? []) {
    const raw = t.replace(/^[{]{2,3}|[}]{2,3}$/g, '').trim();
    const m = /^([\w.]+)\s*\(/.exec(raw); // parenthesized call or args imply helper
    const name = (m?.[1] ?? (raw.includes(' ') ? raw.split(/\s+/)[0] : null));
    if (raw.includes('(') || raw.match(/[\w.]+\s+[^\s=]/)) if (name) called.add(name);
  }
  const missing = [...called].filter(n => !(n in helpers) && n !== 'lookup');
  if (missing.length) throw new Error(`Unregistered helpers used in template: ${missing.join(', ')}`);
}

Try / catch

try {
  return render(src, data, { helpers });
} catch (err) {
  const m = /^Missing helper: "(.+)"$/.exec(err?.message ?? '');
  if (m) throw new Error(`Helper "${m[1]}" must be registered in the helpers option`);
  throw err;
}

Prevention

When it happens

Trigger: Rendering `{{helperName arg}}` or a subexpression `{{concat a b}}` where `helperName` was never registered via the helpers/runtime options and is not `lookup`.

Common situations: Porting Handlebars templates into this engine without porting the custom helper registrations; typo in a helper name; registering helpers on a different render instance than the one rendering; helper removed in a refactor while templates still reference it.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a1654d27b486340d. Report an issue: GitHub.