actualbudget/actual · error
role-does-not-exists
role-does-not-exists
Error message
Selected role does not exist
What it means
The POST /users admin handler rejects a user-creation request whose `role` field does not map to a known role. `UserService.validateRole(role)` returns a falsy role id when the role name is not in the server's role table, so the handler responds 400 with reason 'role-does-not-exists'. This prevents accounts from being created with unusable permissions.
Source
Thrown at packages/sync-server/src/app-admin.js:70
details: 'permission-not-found',
});
return;
}
const { userName, role, displayName, enabled } = req.body || {};
if (!userName || !role) {
res.status(400).send({
status: 'error',
reason: `${!userName ? 'user-cant-be-empty' : 'role-cant-be-empty'}`,
details: `${!userName ? 'Username' : 'Role'} cannot be empty`,
});
return;
}
const roleIdFromDb = UserService.validateRole(role);
if (!roleIdFromDb) {
res.status(400).send({
status: 'error',
reason: 'role-does-not-exists',
details: 'Selected role does not exist',
});
return;
}
const userIdInDb = UserService.getUserByUsername(userName);
if (userIdInDb) {
res.status(400).send({
status: 'error',
reason: 'user-already-exists',
details: `User ${userName} already exists`,
});
return;
}
const userId = uuidv4();View on GitHub (pinned to d4334cb6e6)
Solutions
- Use a canonical role name accepted by validateRole (e.g. 'admin' or 'basic') in the POST /users body.
- Check the role list in the running server version (packages/sync-server/src/util/roles.js) — the role may not exist in your release.
- If the role genuinely should exist, upgrade the sync-server to a version that includes it.
- Normalize case/whitespace on the client before sending the role value.
Example fix
// before
await fetch('/users', { method: 'POST', body: JSON.stringify({ userName: 'jane', role: 'SuperAdmin', password: '...' }) });
// after
await fetch('/users', { method: 'POST', body: JSON.stringify({ userName: 'jane', role: 'admin', password: '...' }) }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_ROLES = ['admin', 'basic'];
function isValidRole(role) {
return typeof role === 'string' && VALID_ROLES.includes(role.trim().toLowerCase());
}
if (!isValidRole(role)) throw new Error(`Invalid role: ${role}`); Type guard
function isKnownRole(r: unknown): r is 'admin' | 'basic' {
return r === 'admin' || r === 'basic';
} Try / catch
try {
const res = await fetch(base + '/users', { method: 'POST', ... });
const body = await res.json();
if (body.reason === 'role-does-not-exists') {
// fall back to a default known role and retry once
}
} catch (e) { /* network failure */ } Prevention
- Keep the role list in sync with the deployed sync-server version.
- Always send lowercase canonical role names from a fixed UI dropdown.
- Add a pre-flight unit test asserting role strings against the server's roles module.
- Trim and lowercase user-supplied role input before sending.
When it happens
Trigger: POST /users (admin session) with body role set to a string not accepted by UserService.validateRole, e.g. role: 'superadmin', role: '' spelled differently than the canonical role names ('admin'/'basic'/'user' in Actual's sync-server), or a numeric role id passed instead of the name.
Common situations: Deploying an older sync-server that lacks a newer role name; custom admin dashboards or scripts sending hand-rolled role values; copying role strings from a different product's API; typos like 'Admin' vs 'admin' if the lookup is case-sensitive.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/d61797e0dc0e3a0d.
Report an issue: GitHub.