actualbudget/actual · error

Unrecognized menu item: ${name}

Error message

Unrecognized menu item: ${name}

What it means

BudgetMenu's onMenuSelect handler maps menu item names to callback props via a switch statement; any name not in the known cases hits the default branch which throws. This is an internal invariant check: the Menu component invokes onMenuSelect(name) with the `name` field of the clicked item, so it should never fire unless the items list and switch are out of sync (or a custom item is spread into the Menu via {...props}).

Source

Thrown at packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx:48

        onCopyLastMonthAverage?.();
        break;
      case 'set-single-3-avg':
        onSetMonthsAverage?.(3);
        break;
      case 'set-single-6-avg':
        onSetMonthsAverage?.(6);
        break;
      case 'set-single-12-avg':
        onSetMonthsAverage?.(12);
        break;
      case 'apply-single-category-template':
        onApplyBudgetTemplate?.();
        break;
      case 'copy-until-year-end':
        onCopyUntilYearEnd?.();
        break;
      default:
        throw new Error(`Unrecognized menu item: ${name}`);
    }
  };

  return (
    <Menu
      {...props}
      onMenuSelect={onMenuSelect}
      items={[
        {
          name: 'copy-single-last',
          text: t("Copy last month's budget"),
        },
        {
          name: 'set-single-3-avg',
          text: t('Set to 3 month average'),
        },
        {
          name: 'set-single-6-avg',

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Add a `case` for the unknown item name in onMenuSelect (packages/desktop-client/src/components/budget/tracking/BudgetMenu.tsx:28) wired to the right callback.
  2. Remove or rename the offending menu item in the `items` array so its `name` matches an existing case.
  3. As a softer fix, replace the throw in the default branch with a console/logger warning so a stray item doesn't crash the budget page.

Example fix

// before
case 'copy-until-year-end':
  onCopyUntilYearEnd?.();
  break;
default:
  throw new Error(`Unrecognized menu item: ${name}`);

// after
case 'copy-until-year-end':
  onCopyUntilYearEnd?.();
  break;
case 'my-new-item':
  onMyNewItem?.();
  break;
default:
  console.warn(`Unrecognized menu item: ${name}`);
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN_ITEMS = ['copy-single-last','set-single-3-avg','set-single-6-avg','set-single-12-avg','apply-single-category-template','copy-until-year-end'];
const isKnownItem = (name: string): boolean => KNOWN_ITEMS.includes(name);
// validate menu items before rendering:
const safeItems = items.filter(item => isKnownItem(item.name));

Type guard

function isBudgetMenuItem(name: string): name is 'copy-single-last' | 'set-single-3-avg' | 'set-single-6-avg' | 'set-single-12-avg' | 'apply-single-category-template' | 'copy-until-year-end' {
  return ['copy-single-last','set-single-3-avg','set-single-6-avg','set-single-12-avg','apply-single-category-template','copy-until-year-end'].includes(name);
}

Try / catch

try {
  onMenuSelect(name);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Unrecognized menu item')) {
    logger.warn(`Ignoring unknown menu item: ${name}`);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Rendering a Menu (spread through {...props}) that contains an item whose `name` is not one of: copy-single-last, set-single-3-avg, set-single-6-avg, set-single-12-avg, apply-single-category-template, copy-until-year-end, and then clicking it.

Common situations: A fork or plugin adds a custom menu item to BudgetMenu without adding a matching case; an upstream rename of an item name (e.g. 'copy-last-month' vs 'copy-single-last') leaves a stale consumer; refactoring the switch but forgetting the conditional goal-templates item.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/35b9b960e5bfd5ad. Report an issue: GitHub.