{"record":{"id":"66260a1ee6eb7fdc","repo":"actualbudget/actual","slug":"only-one-spend-template-is-allowed-per-category","errorCode":null,"errorMessage":"Only one spend template is allowed per category","messagePattern":"Only one spend template is allowed per category","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budget/category-template-context.ts","lineNumber":646,"sourceCode":"      if (this.fromLastMonth >= this.limitAmount) {\n        this.limitMet = true;\n        if (this.limitHold) {\n          this.limitExcess = 0;\n          this.toBudgetAmount = 0;\n          this.fullAmount = 0;\n        } else {\n          this.limitExcess = this.fromLastMonth - this.limitAmount;\n          this.toBudgetAmount = -this.limitExcess;\n          this.fullAmount = -this.limitExcess;\n        }\n      }\n    }\n  }\n\n  private checkSpend() {\n    const st = this.templates.filter(t => t.type === 'spend');\n    if (st.length > 1) {\n      throw new Error('Only one spend template is allowed per category');\n    }\n  }\n\n  private checkGoal() {\n    if (this.goals.length > 1) {\n      throw new Error(`Only one #goal is allowed per category`);\n    }\n  }\n\n  private removeFraction(amount: number): number {\n    return amountToInteger(\n      Math.round(integerToAmount(amount, this.currency.decimalPlaces)),\n      this.currency.decimalPlaces,\n    );\n  }\n\n  //-----------------------------------------------------------------------------\n  //  Processor Functions","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budget/category-template-context.ts#L628-L664","documentation":"Only one `spend` template may exist per category per month. `checkSpend`, invoked from the CategoryTemplateContext constructor, counts templates of type 'spend' for the category and throws if more than one is present, since multiple spend goals would be ambiguous.","triggerScenarios":"A category's templates for the month contain two or more `#template spend ...` (or `template up to spend` variants) lines, e.g. `#template spend 200` and `#template spend from 2024-01-01` both active in the same category.","commonSituations":"Adding a new spend goal without deleting the previous one; combined `by`-derived spend plus an explicit spend line; template notes copied from another category that already had one.","solutions":["Delete one of the spend template lines so only a single spend goal remains for that category","Merge the two goals into one spend template with a single target amount and date"],"exampleFix":"// before\n#template spend 200\n#template spend 300\n// after\n#template spend 300","handlingStrategy":"validation","validationCode":"const spendLines = noteLines.filter(l => /#template\\s+spend/.test(l));\nif (spendLines.length > 1) throw new Error('only one spend template per category');","typeGuard":null,"tryCatchPattern":"try {\n  await init();\n} catch (e) {\n  if (e.message === 'Only one spend template is allowed per category') {\n    // delete or merge one of the spend lines in the category note\n  } else throw e;\n}","preventionTips":["Update the existing spend line rather than adding a second","Search notes for `#template spend` before adding one","Remember `by` templates also imply spend; avoid pairing with an explicit spend line"],"tags":["goal-templates","spend","duplicate","validation"],"backgroundTag":"duplicate-directive","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}