actualbudget/actual · error

user-cant-be-empty

user-cant-be-empty

Error message

Username cannot be empty

What it means

HTTP 400 from POST /users with `reason:'user-cant-be-empty', details:'Username cannot be empty'`. After the admin check, the endpoint requires both `userName` and `role` in the body; when `userName` is falsy it rejects with this dedicated validation reason. (An empty `role` produces the sibling `role-cant-be-empty`.)

Source

Thrown at packages/sync-server/src/app-admin.js:60

      enabled: u.enabled === 1,
    })),
  );
});

app.post('/users', validateSessionMiddleware, async (req, res) => {
  if (!isAdmin(res.locals.user_id)) {
    res.status(403).send({
      status: 'error',
      reason: 'forbidden',
      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);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Include a non-empty `userName` string in the JSON body: `{"userName":"alice","role":"basic"}`.
  2. Check for field-name typos — the API expects exactly `userName` (camelCase), not `username` or `user_name`.
  3. Client-side: guard `if (!userName) throw ...` before calling the endpoint to fail fast with a clearer message.

Example fix

// before
await api.post('/users', { username: name, role: 'basic' });
// after
await api.post('/users', { userName: name, role: 'basic' });
Defensive patterns

Strategy: validation

Validate before calling

function canCreateUser(body) {
  return typeof body?.userName === 'string' && body.userName.trim() !== ''
    && typeof body?.role === 'string' && body.role.trim() !== '';
}
if (!canCreateUser(payload)) throw new Error('userName and role are required');

Type guard

function hasRequiredUserFields(b) {
  return typeof b === 'object' && b !== null
    && typeof b.userName === 'string' && b.userName.length > 0
    && typeof b.role === 'string' && b.role.length > 0;
}

Try / catch

try {
  await post('/users', { userName, role }, { headers: authHeaders(adminToken) });
} catch (e) {
  if (e.response?.data?.reason === 'user-cant-be-empty') throw new ValidationError('userName is required');
  throw e;
}

Prevention

When it happens

Trigger: POST /users by an admin where `userName` is missing, empty string, null, or undefined — e.g. body `{}`, `{role:'basic'}`, or a body that failed JSON parsing so destructuring yields undefined.

Common situations: Scripts building the payload from an unset environment variable (`userName: process.env.NEW_USER` when unset); clients sending the field under a different name (`username`, `name`); empty Content-Type bodies.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


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