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

  1. Use a canonical role name accepted by validateRole (e.g. 'admin' or 'basic') in the POST /users body.
  2. Check the role list in the running server version (packages/sync-server/src/util/roles.js) — the role may not exist in your release.
  3. If the role genuinely should exist, upgrade the sync-server to a version that includes it.
  4. 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

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.