immich-app/immich · error · BadRequestException

Unable to process profile image

Error message

Unable to process profile image

What it means

createProfileImage fails when the uploaded profile image cannot be processed (resizing/conversion pipeline throws). The service queues a FileDelete job to remove the uploaded temp file and wraps the underlying error as a 400 Bad Request with 'Unable to process profile image', preserving the original cause.

Solutions

  1. Inspect the `cause` of the error / server logs to see the underlying processing failure (decode error, unsupported format, etc.).
  2. Re-upload a valid, standard JPEG or PNG file that opens in an image viewer.
  3. Convert unsupported formats (HEIC, etc.) to JPEG/PNG before uploading.
  4. On self-hosted servers, ensure required image codec libraries (e.g. libvips/heif) are installed in the container.
  5. Retry after confirming the upload completed fully (check file size, stable connection).
Defensive patterns

Strategy: validation

Validate before calling

const isValidImage = (file: File) =>
  file.size > 0 && ['image/jpeg', 'image/png', 'image/webp'].includes(file.type);
if (!isValidImage(file)) throw new Error('upload a valid JPEG/PNG/WebP image');

Type guard

const isImageFile = (f: unknown): f is File & { type: 'image/jpeg' | 'image/png' | 'image/webp' } =>
  f instanceof File && /^image\/(jpeg|png|webp)$/.test(f.type);

Try / catch

try {
  await api.createProfileImage(file);
} catch (e) {
  if (e instanceof BadRequestException && e.message === 'Unable to process profile image') {
    logger.warn('profile image rejected', { cause: e.cause });
    // fall back / ask user for a different file
  }
}

Prevention

When it happens

Trigger: POST /users/profile-image with a file whose processing (via config, userId, file.path into the profile image pipeline) throws — e.g. corrupt image, unsupported format, truncated upload, or processing-library failure.

Common situations: Uploading a file renamed to .jpg that is not actually an image; uploading HEIC/AVIF on a build without the required decoder libraries; zero-byte or interrupted downloads; images exceeding processing limits.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/user.service.ts:118

    const user = await this.findOrFail(id, { withDeleted: false });
    return mapUser(user);
  }

  async createProfileImage(auth: AuthDto, file: Express.Multer.File): Promise<CreateProfileImageResponseDto> {
    const { profileImagePath: oldPath } = await this.findOrFail(auth.user.id, { withDeleted: false });

    let profileImagePath: string;
    try {
      const config = await this.getConfig({ withCache: true });
      profileImagePath = await generateProfileImage(
        { media: this.mediaRepository, crypto: this.cryptoRepository, storageCore: this.storageCore },
        config,
        auth.user.id,
        file.path,
      );
    } catch (error) {
      await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [file.path] } });
      throw new BadRequestException('Unable to process profile image', { cause: error });
    }

    const user = await this.userRepository.update(auth.user.id, {
      profileImagePath,
      profileChangedAt: new Date(),
    });

    const toDelete = [file.path, ...(oldPath ? [oldPath] : [])];
    await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: toDelete } });

    return {
      userId: user.id,
      profileImagePath: user.profileImagePath,
      profileChangedAt: user.profileChangedAt,
    };
  }

  async deleteProfileImage(auth: AuthDto): Promise<void> {

View on GitHub (pinned to e55ac299a4)