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

  1. Query schedules for the desired name first and pick a unique name if taken
  2. Append a discriminator (date/suffix) to generated names to guarantee uniqueness
  3. 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

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


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