actualbudget/actual · error · APIError

Field ${typedKey} is system-managed and not user-editable.

Error message

Field ${typedKey} is system-managed and not user-editable.

What it means

Schedule fields next_date and completed are derived by Actual from the schedule's date condition and completed status, not stored as freely editable fields. api.updateSchedule() rejects updates targeting these keys with this APIError.

Source

Thrown at packages/loot-core/src/server/api.ts:983

    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;
        } else {
          sched._conditions.push({
            field: 'payee',
            op: 'is',
            value: String(value),
          });

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Change next_date by editing the schedule's date condition (update the 'date' entry in sched._conditions / pass fields accepted for conditions)
  2. Remove next_date/completed from the fields you send — let Actual compute them
  3. If the goal is to pause a schedule, use the schedule's completed flag through the dedicated internal handler or the UI instead of updateSchedule

Example fix

// before
await api.updateSchedule(id, { next_date: '2026-09-01' });
// after
await api.updateSchedule(id, {
  conditions: [{ field: 'date', op: 'is', value: '2026-09-01' }],
  conditionsOp: 'and',
});
Defensive patterns

Strategy: validation

Validate before calling

if (key === 'next_date' || key === 'completed') {
  throw APIError(`Field ${key} is system-managed and not user-editable.`);
}

Type guard

function isSystemManagedField(key) {
  return key === 'next_date' || key === 'completed';
}

Try / catch

try {
  await updateSchedule(id, fields);
} catch (e) {
  if (/system-managed/.test(e.message)) {
    const { [field]: _, ...rest } = fields;
    return updateSchedule(id, rest);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling api.updateSchedule(id, { next_date: '2026-01-01' }) or { completed: true } — the handler's switch on the field key hits the next_date/completed case and throws.

Common situations: Code that treats schedules like generic records and iterates over all field changes; bulk sync scripts copying schedule fields from another budget; attempts to mark schedules complete programmatically.

Related errors


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