{"record":{"id":"c8c47740fb5d2e1b","repo":"actualbudget/actual","slug":"there-is-already-a-schedule-named-newname","errorCode":null,"errorMessage":"There is already a schedule named: ${newName}","messagePattern":"There is already a schedule named: (.+?)","errorType":"validation","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/api.ts","lineNumber":977,"sourceCode":"  const accountIndex = sched._conditions.findIndex(c => c.field === 'account');\n  const dateIndex = sched._conditions.findIndex(c => c.field === 'date');\n  const amountIndex = sched._conditions.findIndex(c => c.field === 'amount');\n\n  for (const key in fields) {\n    const typedKey = key as keyof APIScheduleEntity;\n    const value = fields[typedKey];\n\n    switch (typedKey) {\n      case 'name': {\n        const newName = String(value);\n        const { data: existing } = await aqlQuery(\n          q('schedules').filter({ name: newName }).select('*'),\n        );\n        if (!existing || existing.length === 0 || existing[0].id === sched.id) {\n          sched.name = newName;\n          conditionsUpdated = true;\n        } else {\n          throw APIError(`There is already a schedule named: ${newName}`);\n        }\n        break;\n      }\n      case 'next_date':\n      case 'completed': {\n        throw APIError(\n          `Field ${typedKey} is system-managed and not user-editable.`,\n        );\n      }\n      case 'posts_transaction': {\n        sched.posts_transaction = Boolean(value);\n        conditionsUpdated = true;\n        break;\n      }\n      case 'payee': {\n        if (payeeIndex !== -1) {\n          sched._conditions[payeeIndex].value = value;\n          conditionsUpdated = true;","sourceCodeStart":959,"sourceCodeEnd":995,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/api.ts#L959-L995","documentation":"When api.updateSchedule() receives a 'name' field, the handler checks the schedules table for an existing schedule with that name. The rename only proceeds if no other schedule (by id) already holds the name; otherwise it throws this APIError.","triggerScenarios":"Calling api.updateSchedule(id, { name: newName }) where a different schedule (existing[0].id !== sched.id) already uses newName. Renaming to the schedule's own current name is allowed.","commonSituations":"Renaming schedules from imported data where names collide; automation generating names from templates that repeat (e.g. 'Monthly rent'); users renaming via API while a duplicate already exists in the UI.","solutions":["Query schedules for the desired name first and pick a unique name if taken","Append a discriminator (date/suffix) to generated names to guarantee uniqueness","If the collision is the same schedule, no change is needed — the API allows renaming to its own name"],"exampleFix":"// before\nawait api.updateSchedule(id, { name: 'Rent' });\n// after\nconst { data } = await api.aqlQuery(q('schedules').filter({ name: 'Rent' }).select('*'));\nconst name = data && data.length ? `Rent ${new Date().toISOString().slice(0, 10)}` : 'Rent';\nawait api.updateSchedule(id, { name });","handlingStrategy":"validation","validationCode":"const { data: existing } = await aqlQuery(q('schedules').filter({ name: newName }).select('*'));\nif (existing && existing.length > 0 && existing[0].id !== sched.id) {\n  throw APIError(`There is already a schedule named: ${newName}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  sched.name = newName;\n} catch (e) {\n  if (/already a schedule named/.test(e.message)) {\n    newName = `${newName}-2`;\n    // retry once\n  } else throw e;\n}","preventionTips":["Add a unique constraint/migration on schedules.name","Offer name-suggestions when a collision occurs","Test renames against duplicate names"],"tags":["api","schedules","uniqueness","validation"],"backgroundTag":"duplicate-name-conflict","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}