actualbudget/actual · error
Unknown display type: ${String(displayType)}
Error message
Unknown display type: ${String(displayType)} What it means
getDisplayTemplateMeta maps each DisplayTemplateType to label/description/icon metadata. The default arm uses `displayType satisfies never` as an exhaustiveness assertion: at runtime, receiving a value outside the known union means corrupted or unhandled display type state, so it throws with the raw value in the message.
Source
Thrown at packages/desktop-client/src/components/budget/goals/displayTemplateMeta.ts:100
case 'remainder':
return {
label: t('Whatever is left'),
description: t(
'Split any remaining To Budget across these categories.',
),
icon: SvgShare,
};
case 'goal':
return {
label: t('Long-term goal'),
description: t(
'Set a long-term savings target. This changes the coloring of the balance on the budget page to be based on progress towards the target rather than the current month funding progress.',
),
icon: SvgFlag,
};
default:
displayType satisfies never;
throw new Error(`Unknown display type: ${String(displayType)}`);
}
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Fix the displayType value stored in the reducer/template state to a valid union member
- Add a case for any newly introduced DisplayTemplateType in the switch
- Clear or migrate stale template state (e.g. re-save the category template) after upgrading Actual
- If the value comes from user-supplied input, validate it against the union before calling
Example fix
// before
const meta = getDisplayTemplateMeta(someState.displayType);
// after
const KNOWN = ['fixed','schedule','by','percentage','historical','limit','refill','remainder','goal'] as const;
const meta = KNOWN.includes(someState.displayType)
? getDisplayTemplateMeta(someState.displayType)
: getDisplayTemplateMeta('fixed'); Defensive patterns
Strategy: type-guard
Validate before calling
const KNOWN_DISPLAY_TYPES = ['fixed','schedule','by','percentage','historical','limit','refill','remainder','goal'] as const;
if (!KNOWN_DISPLAY_TYPES.includes(displayType)) {
throw new Error(`Unsupported display type: ${displayType}`);
}
const meta = getDisplayTemplateMeta(displayType); Type guard
const isDisplayTemplateType = (v: string): v is DisplayTemplateType => ['fixed','schedule','by','percentage','historical','limit','refill','remainder','goal'].includes(v);
Try / catch
let meta;
try {
meta = getDisplayTemplateMeta(displayType);
} catch (e) {
logger.warn('Unknown display type, falling back', displayType);
meta = getDisplayTemplateMeta('fixed');
} Prevention
- Rely on the `satisfies never` exhaustiveness check to catch new union members at compile time
- Validate persisted displayType values after version migrations
- Never construct ReducerState.displayType from unchecked strings
When it happens
Trigger: Calling getDisplayTemplateMeta with a displayType string not in the union ('fixed'|'schedule'|'by'|'percentage'|'historical'|'limit'|'refill'|'remainder'|'goal') — e.g. state loaded from older persisted data or a hand-built ReducerState with a typo.
Common situations: Version migration where a display type was renamed/removed but old template state still holds the old value; plugin/custom code constructing a ReducerState manually; a new DisplayTemplateType added to the union without a case here (caught by satisfies never at compile time, but not in JS builds).
Related errors
- Unknown template type: ${String(type satisfies undefined)}
- Unknown display type: ${String(visualType satisfies never)}
- Unhandled template type
- Template cannot be null
- An error occurred while parsing the template
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/7392221e96ff9026.
Report an issue: GitHub.