immich-app/immich · error · BadRequestException

The first registered account must the administrator.

Error message

The first registered account must the administrator.

What it means

Immich requires that the very first user account created in the system is the administrator. When creating a user with isAdmin=false (or unset), the service checks userRepository.getAdmin(); if no admin exists yet, creation of a non-admin account is rejected with this BadRequestException. This guarantees a bootstrap admin always exists.

Solutions

  1. Create the first account with isAdmin=true so it becomes the administrator.
  2. Verify an admin exists: run the server's admin-check or query the user table for a row with is_admin=true.
  3. If the admin was deleted, restore it or re-run initial setup before adding regular users.

Example fix

// before
await api.createUser({ email: 'bob@example.com', password: 'pw', name: 'Bob' }); // isAdmin defaults to false
// after
await api.createUser({ email: 'admin@example.com', password: 'pw', name: 'Admin', isAdmin: true }); // first user must be admin
Defensive patterns

Strategy: validation

Validate before calling

// Before creating a user, check whether an admin already exists
const hasAdmin = (await api.searchUsers()).some(u => u.isAdmin);
if (!hasAdmin) {
  dto.isAdmin = true; // first user must be the administrator
}

Prevention

When it happens

Trigger: Calling POST /api/admin/users (createUser) with dto.isAdmin=false (or omitted) while no administrator account exists in the database yet.

Common situations: Fresh Immich installs where the first API-created user is a regular user; scripts provisioning users before the setup wizard has created the admin; restored databases where the admin row was dropped.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/1a3c532eb0f9816c. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/base.service.ts:313

  }

  async requireSetupAvailable(): Promise<void> {
    if (!(await this.isSetupAvailable())) {
      throw new BadRequestException('Admin setup is not available');
    }
  }

  async createUser(dto: Omit<Insertable<UserTable>, 'clusterGroupId'> & { email: string }): Promise<UserAdmin> {
    const exists = await this.userRepository.getByEmail(dto.email);
    if (exists) {
      this.logger.debug('User creation rejected: user already exists');
      throw new BadRequestException('Email is not available');
    }

    if (!dto.isAdmin) {
      const localAdmin = await this.userRepository.getAdmin();
      if (!localAdmin) {
        throw new BadRequestException('The first registered account must the administrator.');
      }
    }

    const payload: Omit<Insertable<UserTable>, 'clusterGroupId'> = { ...dto };
    if (payload.password) {
      payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
    }
    if (payload.storageLabel) {
      payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
    }

    const clusterGroup = await this.clusterGroupRepository.create();
    const user = await this.userRepository.create({ ...payload, clusterGroupId: clusterGroup.id });

    await this.eventRepository.emit('UserCreate', user);

    return user;
  }

View on GitHub (pinned to e55ac299a4)