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
- 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.
- Remove or rename the offending menu item in the `items` array so its `name` matches an existing case.
- 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
- Type menu item names as a string-literal union shared by the items array and the switch.
- Drive the switch from a Record<ItemName, () => void> map so missing handlers are compile errors.
- Never inject extra items into BudgetMenu via prop spread without extending the component.
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
- Unrecognized menu option: ${name}
- Unrecognized menu option: ${String(item)}
- Unrecognized menu option: ${String(name)}
- Unrecognized menu item: ${name}
- Unrecognized menu option: ${String(name)}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/35b9b960e5bfd5ad.
Report an issue: GitHub.