actualbudget/actual · error
Unknown item type:
Error message
Unknown item type:
What it means
BudgetCategories' item renderer switches on a discriminated union of budget item types; the default branch is unreachable for well-typed input (hence `item.type` is `never` and the @ts-expect-error). Throwing here guards against data that violates the union at runtime, e.g. a type string produced outside the known set.
Source
Thrown at packages/desktop-client/src/components/budget/BudgetCategories.tsx:376
content = (
<IncomeCategory
cat={item.value}
editingCell={editingCell}
isLast={idx === items.length - 1}
onEditName={onEditName}
onEditMonth={onEditMonth}
onSave={_onSaveCategory}
onDelete={onDeleteCategory}
onDragChange={onDragChange}
onReorder={onReorderCategory}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
/>
);
break;
default:
// @ts-expect-error Error is expected here because "item.type" is "never"
throw new Error('Unknown item type: ' + item.type);
}
const pos =
idx === 0 ? 'first' : idx === items.length - 1 ? 'last' : null;
return (
<DropHighlightPosContext.Provider
key={
'value' in item
? item.value.id
: item.type === 'income-separator'
? 'separator'
: idx
}
value={pos}
>
<View
style={View on GitHub (pinned to d4334cb6e6)
Solutions
- Add a case for the unexpected item.type to the switch so the union is fully handled (TypeScript exhaustiveness will then flag future additions).
- Render a neutral fallback (e.g. return null or a placeholder row) in the default branch instead of crashing the whole budget view.
- Log the offending type (via logger) before handling, to diagnose where the unknown row originates.
Example fix
// before
default:
// @ts-expect-error
throw new Error('Unknown item type: ' + item.type);
// after
default:
console.warn('Unknown budget item type:', item.type);
return null; Defensive patterns
Strategy: type-guard
Validate before calling
const KNOWN_TYPES = ['income', 'total-spend', 'total-income', 'total-leftover'] as const; type KnownType = typeof KNOWN_TYPES[number]; if (!KNOWN_TYPES.includes(item.type as KnownType)) return null; // skip unknown rows
Type guard
function isKnownBudgetItem(item: BudgetItem): item is BudgetItem & { type: KnownType } {
return (KNOWN_TYPES as readonly string[]).includes(item.type);
} Try / catch
try {
rows.push(renderItem(item));
} catch (e) {
if (String(e.message).startsWith('Unknown item type')) {
logger.warn('skipping unknown budget item', item);
return null;
} else throw e;
} Prevention
- Extend the switch whenever a new budget item type is added to the union.
- Rely on the `never` exhaustiveness check instead of bypassing it silently.
- Render a fallback row instead of throwing so one bad row can't break the budget view.
When it happens
Trigger: Rendering a budget row whose item.type is not one of the handled kinds (e.g. 'income', 'total-spend', etc.) — typically after a new item type is added to the data layer without extending this switch, or corrupted/generated rows carrying an unexpected type.
Common situations: A contributor adds a new budget summary row type and forgets the BudgetCategories switch; plugin/template code injecting rows with a custom type; version skew between the bundled app and data produced by a newer release.
Related errors
- Unrecognized menu option: ${name}
- Unrecognized menu item: ${name}
- Unrecognized menu option: ${String(name)}
- Unknown display type: ${String(displayType)}
- Unknown template type: ${String(type satisfies undefined)}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/297f842b53681a52.
Report an issue: GitHub.