actualbudget/actual · error

Unrecognized periodic period: ${String(period)}

Error message

Unrecognized periodic period: ${String(period)}

What it means

This error is thrown by CategoryTemplateContext when processing a `periodic` budget template whose `period` option is not one of the supported values (day, week, month, year). The switch statement maps each period to a date-shift function; the default branch rejects anything else. It is a template-definition validation error raised at budget-template application time.

Source

Thrown at packages/loot-core/src/server/budget/category-template-context.ts:733

    let dateShiftFunction;
    switch (period) {
      case 'day':
        dateShiftFunction = monthUtils.addDays;
        break;
      case 'week':
        dateShiftFunction = monthUtils.addWeeks;
        break;
      case 'month':
        dateShiftFunction = monthUtils.addMonths;
        break;
      case 'year':
        // the addYears function doesn't return the month number, so use addMonths
        dateShiftFunction = (date: string | Date, numPeriods: number) =>
          monthUtils.addMonths(date, numPeriods * 12);
        break;
      default:
        throw new Error(`Unrecognized periodic period: ${String(period)}`);
    }

    //shift the starting date until its in our month or in the future
    while (templateContext.month > date) {
      date = dateShiftFunction(date, numPeriods);
    }

    if (
      monthUtils.differenceInCalendarMonths(templateContext.month, date) < 0
    ) {
      return 0;
    } // nothing needed this month

    const nextMonth = monthUtils.addMonths(templateContext.month, 1);
    while (date < nextMonth) {
      toBudget += amount;
      date = dateShiftFunction(date, numPeriods);
    }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Change the template line to use a supported period: day, week, month, or year (e.g. `#template periodic 100 every 1 month`).
  2. Check spelling/normalization of the period string before applying templates.
  3. If programmatically building templates, validate the period against the allowed set before calling the template context.
  4. If a new period seems needed, verify the installed Actual version supports it or open a feature request.

Example fix

// before
#template periodic 50 every 2 weeks-ish
// after
#template periodic 50 every 2 weeks
Defensive patterns

Strategy: validation

Validate before calling

const PERIODS = ['day', 'week', 'month', 'year'] as const;
type Period = (typeof PERIODS)[number];
if (!PERIODS.includes(period as Period)) {
  throw new Error(`period must be one of ${PERIODS.join(', ')}, got: ${period}`);
}

Type guard

function isPeriod(p: unknown): p is 'day' | 'week' | 'month' | 'year' {
  return typeof p === 'string' && ['day', 'week', 'month', 'year'].includes(p);
}

Try / catch

try {
  await applyTemplates(categoryIds, month);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Unrecognized periodic period')) {
    logger.warn('Skipping category with invalid periodic template', { error: e.message });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A budget template line like `#template periodic 100 every 3 fortnights` or any misspelled period (e.g. `weekly`, `montly`, `yr`) is parsed and passed as `period` to the periodic template builder, hitting the switch default.

Common situations: Typos in template comments in category notes, copy-pasted templates from older docs or other tools, localized period names, or a renamed/removed period keyword after a version change.

Related errors


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