actualbudget/actual · error

Income category "${template.category}" not found for percent

Error message

Income category "${template.category}" not found for percentage template

What it means

The `percentage` budget template distributes a percentage of an income category's monthly income into a category. This error is thrown when the category named/id given in the template does not match any existing income category (is_income) in the budget. The lookup matches by id or case-insensitive name, so a mismatch means the category doesn't exist or isn't marked as income.

Source

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

      );
    } else {
      sheetName = monthUtils.sheetForMonth(templateContext.month);
    }
    if (cat === 'all income') {
      monthlyIncome = await getSheetValue(sheetName, `total-income`);
    } else if (cat === 'available funds') {
      monthlyIncome = availableFunds;
    } else {
      // Text templates address income categories by name (e.g. `#template
      // 10% of Salary`); the UI's CategoryAutocomplete stores the category
      // id. Accept either form.
      const incomeCat = (await db.getCategories()).find(
        c =>
          c.is_income &&
          (c.id === template.category || c.name.toLocaleLowerCase() === cat),
      );
      if (!incomeCat) {
        throw new Error(
          `Income category "${template.category}" not found for percentage template`,
        );
      }
      monthlyIncome = await getSheetValue(
        sheetName,
        `sum-amount-${incomeCat.id}`,
      );
    }

    return Math.max(0, Math.round(monthlyIncome * (percent / 100)));
  }

  static async runAverage(
    template: AverageTemplate,
    templateContext: CategoryTemplateContext,
  ): Promise<number> {
    let average = await getCategoryAverage({
      month: templateContext.month,

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Verify the category name in the template exactly matches an existing income category (matching is case-insensitive by name or by category id).
  2. Mark the intended category as income ('Income' type in category group settings) if it should be usable here.
  3. Fix or remove the stale `#template ... % of <category>` line in the category note.
  4. Query the budget (API/db) to list income categories and use one of those exact names.

Example fix

// before
#template 10% of Paycheck   // Paycheck is an expense category
// after (Paycheck marked as income, or use an income category)
#template 10% of Income
Defensive patterns

Strategy: validation

Validate before calling

const cats = await db.getCategories();
const incomeCat = cats.find(
  c => c.is_income && c.name.toLocaleLowerCase() === template.category.toLocaleLowerCase(),
);
if (!incomeCat) {
  const names = cats.filter(c => c.is_income).map(c => c.name);
  throw new Error(`'${template.category}' is not an income category. Available: ${names.join(', ')}`);
}

Type guard

function isIncomeCategory(
  cats: { id: string; name: string; is_income: boolean }[],
  name: string,
): boolean {
  return cats.some(c => c.is_income && c.name.toLocaleLowerCase() === name.toLocaleLowerCase());
}

Try / catch

try {
  await applyTemplates(categoryIds, month);
} catch (e) {
  if (e instanceof Error && e.message.includes('not found for percentage template')) {
    logger.warn('Percentage template references missing income category; skipping', { error: e.message });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A template line like `#template 10% of Income` where `Income` is not an existing category name (typo, renamed category, deleted category, or the category exists but is not flagged as income).

Common situations: Category renamed or deleted after the template was written, using an on-budget expense category instead of an income category, restoring a budget where income categories were re-created with new ids, or case/punctuation differences in the name.

Related errors


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