actualbudget/actual · error · APIError
There is already a schedule named: ${newName}
Error message
There is already a schedule named: ${newName} What it means
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.
Source
Thrown at packages/loot-core/src/server/api.ts:977
const accountIndex = sched._conditions.findIndex(c => c.field === 'account');
const dateIndex = sched._conditions.findIndex(c => c.field === 'date');
const amountIndex = sched._conditions.findIndex(c => c.field === 'amount');
for (const key in fields) {
const typedKey = key as keyof APIScheduleEntity;
const value = fields[typedKey];
switch (typedKey) {
case 'name': {
const newName = String(value);
const { data: existing } = await aqlQuery(
q('schedules').filter({ name: newName }).select('*'),
);
if (!existing || existing.length === 0 || existing[0].id === sched.id) {
sched.name = newName;
conditionsUpdated = true;
} else {
throw APIError(`There is already a schedule named: ${newName}`);
}
break;
}
case 'next_date':
case 'completed': {
throw APIError(
`Field ${typedKey} is system-managed and not user-editable.`,
);
}
case 'posts_transaction': {
sched.posts_transaction = Boolean(value);
conditionsUpdated = true;
break;
}
case 'payee': {
if (payeeIndex !== -1) {
sched._conditions[payeeIndex].value = value;
conditionsUpdated = true;View on GitHub (pinned to d4334cb6e6)
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
Example fix
// before
await api.updateSchedule(id, { name: 'Rent' });
// after
const { data } = await api.aqlQuery(q('schedules').filter({ name: 'Rent' }).select('*'));
const name = data && data.length ? `Rent ${new Date().toISOString().slice(0, 10)}` : 'Rent';
await api.updateSchedule(id, { name }); Defensive patterns
Strategy: validation
Validate before calling
const { data: existing } = await aqlQuery(q('schedules').filter({ name: newName }).select('*'));
if (existing && existing.length > 0 && existing[0].id !== sched.id) {
throw APIError(`There is already a schedule named: ${newName}`);
} Try / catch
try {
sched.name = newName;
} catch (e) {
if (/already a schedule named/.test(e.message)) {
newName = `${newName}-2`;
// retry once
} else throw e;
} Prevention
- Add a unique constraint/migration on schedules.name
- Offer name-suggestions when a collision occurs
- Test renames against duplicate names
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Field ${typedKey} is system-managed and not user-editable.
- Invalid amount operator: ${String(value)}. Expected: is, isa
- Unknown payee name normalization: ${String(normalization)}
- `date` is required when adding a transaction
- Amount is invalid, must be an integer: ${trans.amount}
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/c8c47740fb5d2e1b.
Report an issue: GitHub.