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
- Create the first account with isAdmin=true so it becomes the administrator.
- Verify an admin exists: run the server's admin-check or query the user table for a row with is_admin=true.
- 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
- Always bootstrap with an admin account before provisioning regular users.
- When scripting user creation on a fresh instance, set isAdmin=true for the first call.
- Never delete the last admin account; promote a replacement first.
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
- Admin account does not exist
- Asset does not have valid dimensions
- Asset not found
- Asset not in stack
- assetIds, albumId, or userId is required
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)