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

  1. Log JSON.stringify(template) at the throw site to capture the rogue type value.
  2. Add a case for the new/unknown template type if it is legitimate, mapping it to the right DisplayTemplateType.
  3. Validate/normalize templates (e.g. filter to known types) before calling migrateTemplatesToAutomations.
  4. 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

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


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