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
- Change next_date by editing the schedule's date condition (update the 'date' entry in sched._conditions / pass fields accepted for conditions)
- Remove next_date/completed from the fields you send — let Actual compute them
- 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
- Publish an explicit READONLY_FIELDS list for schedules
- Strip system fields from any generic diffing/sync code
- Route next_date/completed changes through the date condition or dedicated handlers
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
- There is already a schedule named: ${newName}
- 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/596ce0382eae6a23.
Report an issue: GitHub.