actualbudget/actual · error
Unhandled template type
Error message
Unhandled template type
What it means
The default branch of getDisplayTypeFromTemplate uses a `const _exhaustive: never = template` assertion: for a well-typed Template union this branch is unreachable, so reaching it means template.type held a value outside the known Template type union at runtime. It is a compile-time-exhaustiveness safety net that catches untyped or corrupt template data during the templates-to-automations migration.
Source
Thrown at packages/desktop-client/src/components/modals/BudgetAutomationsModal/migrateTemplatesToAutomations.ts:45
case 'refill':
return 'refill';
case 'average':
case 'copy':
return 'historical';
case 'by':
case 'spend':
return 'by';
case 'remainder':
return 'remainder';
case 'goal':
return 'goal';
case 'error':
// filtered upstream by hasUnsupportedDirective; surface if it ever isn't
throw new Error(`Unsupported template type reached migration`);
default: {
const _exhaustive: never = template;
void _exhaustive;
throw new Error(`Unhandled template type`);
}
}
}
// Fill in scheduleId from name (or refresh a stale name from scheduleId) so
// the editor and engine consistently see both.
function hydrateScheduleTemplate(
template: ScheduleTemplate,
schedules: readonly ScheduleEntity[],
): ScheduleTemplate {
const schedule = template.scheduleId
? schedules.find(s => s.id === template.scheduleId)
: template.name
? schedules.find(s => s.name?.trim() === template.name?.trim())
: undefined;
if (!schedule) return template;
return {
...template,View on GitHub (pinned to d4334cb6e6)
Solutions
- Log JSON.stringify(template) at the throw site to capture the rogue type value.
- Add a case for the new/unknown template type if it is legitimate, mapping it to the right DisplayTemplateType.
- Validate/normalize templates (e.g. filter to known types) before calling migrateTemplatesToAutomations.
- Fix the upstream parser so it only produces Template types present in the type union.
Example fix
// before templates.forEach(t => migrate(t)); // after templates.filter(t => KNOWN_TEMPLATE_TYPES.has(t.type)).forEach(t => migrate(t));
Defensive patterns
Strategy: validation
Validate before calling
const KNOWN_TYPES = new Set(['percentage','schedule','periodic','simple','limit','refill','average','copy','by','spend','remainder','goal']); const safe = templates.filter(t => KNOWN_TYPES.has((t as Template).type)); migrateTemplatesToAutomations(safe, schedules);
Type guard
function hasKnownType(t: Template): boolean {
return KNOWN_TEMPLATE_TYPE_UNION.includes(t.type as never);
} Try / catch
try {
migrateTemplatesToAutomations(templates, schedules);
} catch (err) {
if (String(err).includes('Unhandled template type')) {
logger.error('Unrecognized template during migration', err);
return partialMigrationResult;
}
throw err;
} Prevention
- Normalize/deserialize templates through a validated parser that only emits known union members.
- When adding a new Template type, update this switch, the type union, and the parser in one change.
- Keep the `const _exhaustive: never` assertion in every consumer switch of Template.
- Test migration against fixtures from all historical budget data formats.
When it happens
Trigger: Migrating a template list containing a Template whose `type` is not any known literal (e.g. data from an older budget format, a hand-edited note directive parsed into an unexpected type, or an object cast to Template incorrectly).
Common situations: Version skew between stored budget data and the current Template type union; plugins/custom code building Template objects; a parser regression emitting a type value the union no longer contains.
Related errors
- Unknown display type: ${String(displayType)}
- Unknown template type: ${String(type satisfies undefined)}
- Unknown display type: ${String(visualType satisfies never)}
- Unknown display type: ${String(type satisfies never)}
- Unsupported template type reached migration
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/01d8e0a70c967b0e.
Report an issue: GitHub.