toeverything/AFFiNE · error · ImageFormatNotSupported

image_format_not_supported

image_format_not_supported

Error message

Image format not supported: ${format}

What it means

Avatar uploads sniff the file's magic bytes, cross-checking the declared mimetype. If sniffing cannot identify a type at all, or the identified type does not start with 'image/', the upload is rejected with image_format_not_supported ('unknown' when sniffing failed) before any image processing runs.

Solutions

  1. Upload actual raster images (png/jpeg/webp/gif)
  2. Validate the file type client-side (extension + magic bytes) before starting the upload
  3. Convert exotic formats to png/webp before uploading

Example fix

// before
await uploadAvatar(file); // pdf renamed to .png -> image_format_not_supported

// after
if (!file.type.startsWith('image/')) throw new Error('pick an image file');
await uploadAvatar(file);
Defensive patterns

Strategy: validation

Validate before calling

// client-side gate before upload
if (!file.type.startsWith('image/')) {
  throw new Error('please choose an image file');
}
if (file.size > 5 * 1024 * 1024) {
  throw new Error('image must be under 5MB');
}

Type guard

function isImageFile(file: File): boolean {
  return file.type.startsWith('image/');
}

Try / catch

try {
  await uploadAvatar(file);
} catch (e) {
  if (e?.extensions?.code === 'IMAGE_FORMAT_NOT_SUPPORTED') showFormatError();
  else throw e;
}

Prevention

When it happens

Trigger: Uploading a non-image (pdf, zip, text) renamed to .png; an empty or truncated file whose bytes cannot be sniffed; formats the sniffer does not recognize.

Common situations: Automated imports feeding arbitrary files; drag-and-drop grabbing the wrong file; mislabeled files produced by other tooling.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/87891d5ba5135d55. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/user/resolver.ts:130

    name: 'uploadAvatar',
    description: 'Upload user avatar',
  })
  async uploadAvatar(
    @CurrentUser() user: CurrentUser,
    @Args({ name: 'avatar', type: () => GraphQLUpload })
    avatar: FileUpload
  ) {
    if (!user) {
      throw new UserNotFound();
    }

    const avatarBuffer = await readBufferWithLimit(
      avatar.createReadStream(),
      5 * OneMB
    );
    const contentType = sniffMime(avatarBuffer, avatar.mimetype)?.toLowerCase();
    if (!contentType || !contentType.startsWith('image/')) {
      throw new ImageFormatNotSupported({ format: contentType || 'unknown' });
    }

    let processedAvatarBuffer: Buffer;
    try {
      processedAvatarBuffer = await processImage(avatarBuffer, 512, false);
    } catch {
      throw new ImageFormatNotSupported({ format: contentType });
    }

    const avatarUrl = await this.storage.put(
      `${user.id}-avatar-${Date.now()}`,
      processedAvatarBuffer,
      { contentType: 'image/webp' }
    );

    if (user.avatarUrl) {
      await this.storage.delete(user.avatarUrl);
    }

View on GitHub (pinned to 2af30773ae)