{"record":{"id":"c11b5cc3991887cd","repo":"actualbudget/actual","slug":"income-category-template-category-not-found-f","errorCode":null,"errorMessage":"Income category \"${template.category}\" not found for percentage template","messagePattern":"Income category \"(.+?)\" not found for percentage template","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budget/category-template-context.ts","lineNumber":858,"sourceCode":"      );\n    } else {\n      sheetName = monthUtils.sheetForMonth(templateContext.month);\n    }\n    if (cat === 'all income') {\n      monthlyIncome = await getSheetValue(sheetName, `total-income`);\n    } else if (cat === 'available funds') {\n      monthlyIncome = availableFunds;\n    } else {\n      // Text templates address income categories by name (e.g. `#template\n      // 10% of Salary`); the UI's CategoryAutocomplete stores the category\n      // id. Accept either form.\n      const incomeCat = (await db.getCategories()).find(\n        c =>\n          c.is_income &&\n          (c.id === template.category || c.name.toLocaleLowerCase() === cat),\n      );\n      if (!incomeCat) {\n        throw new Error(\n          `Income category \"${template.category}\" not found for percentage template`,\n        );\n      }\n      monthlyIncome = await getSheetValue(\n        sheetName,\n        `sum-amount-${incomeCat.id}`,\n      );\n    }\n\n    return Math.max(0, Math.round(monthlyIncome * (percent / 100)));\n  }\n\n  static async runAverage(\n    template: AverageTemplate,\n    templateContext: CategoryTemplateContext,\n  ): Promise<number> {\n    let average = await getCategoryAverage({\n      month: templateContext.month,","sourceCodeStart":840,"sourceCodeEnd":876,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budget/category-template-context.ts#L840-L876","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify the category name in the template exactly matches an existing income category (matching is case-insensitive by name or by category id).","Mark the intended category as income ('Income' type in category group settings) if it should be usable here.","Fix or remove the stale `#template ... % of <category>` line in the category note.","Query the budget (API/db) to list income categories and use one of those exact names."],"exampleFix":"// before\n#template 10% of Paycheck   // Paycheck is an expense category\n// after (Paycheck marked as income, or use an income category)\n#template 10% of Income","handlingStrategy":"validation","validationCode":"const cats = await db.getCategories();\nconst incomeCat = cats.find(\n  c => c.is_income && c.name.toLocaleLowerCase() === template.category.toLocaleLowerCase(),\n);\nif (!incomeCat) {\n  const names = cats.filter(c => c.is_income).map(c => c.name);\n  throw new Error(`'${template.category}' is not an income category. Available: ${names.join(', ')}`);\n}","typeGuard":"function isIncomeCategory(\n  cats: { id: string; name: string; is_income: boolean }[],\n  name: string,\n): boolean {\n  return cats.some(c => c.is_income && c.name.toLocaleLowerCase() === name.toLocaleLowerCase());\n}","tryCatchPattern":"try {\n  await applyTemplates(categoryIds, month);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('not found for percentage template')) {\n    logger.warn('Percentage template references missing income category; skipping', { error: e.message });\n  } else {\n    throw e;\n  }\n}","preventionTips":["Reference income categories by their exact current name in templates","Re-check template notes after renaming or deleting categories","Ensure the target category is marked as income before using it in a % template","Use category ids instead of names when building templates programmatically"],"tags":["budget-template","validation","category-lookup"],"backgroundTag":"referenced-category-not-found","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}