{"record":{"id":"87891d5ba5135d55","repo":"toeverything/AFFiNE","slug":"image-format-not-supported","errorCode":"image_format_not_supported","errorMessage":"Image format not supported: ${format}","messagePattern":"Image format not supported: (.+?)","errorType":"exception","errorClass":"ImageFormatNotSupported","httpStatus":400,"severity":"warning","filePath":"packages/backend/server/src/core/user/resolver.ts","lineNumber":128,"sourceCode":"    name: 'uploadAvatar',\n    description: 'Upload user avatar',\n  })\n  async uploadAvatar(\n    @CurrentUser() user: CurrentUser,\n    @Args({ name: 'avatar', type: () => GraphQLUpload })\n    avatar: FileUpload\n  ) {\n    if (!user) {\n      throw new UserNotFound();\n    }\n\n    const avatarBuffer = await readBufferWithLimit(\n      avatar.createReadStream(),\n      5 * OneMB\n    );\n    const contentType = sniffMime(avatarBuffer, avatar.mimetype)?.toLowerCase();\n    if (!contentType || !contentType.startsWith('image/')) {\n      throw new ImageFormatNotSupported({ format: contentType || 'unknown' });\n    }\n\n    let processedAvatarBuffer: Buffer;\n    try {\n      processedAvatarBuffer = await processImage(avatarBuffer, 512, false);\n    } catch {\n      throw new ImageFormatNotSupported({ format: contentType });\n    }\n\n    const avatarUrl = await this.storage.put(\n      `${user.id}-avatar-${Date.now()}`,\n      processedAvatarBuffer,\n      { contentType: 'image/webp' }\n    );\n\n    if (user.avatarUrl) {\n      await this.storage.delete(user.avatarUrl);\n    }","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/user/resolver.ts#L110-L146","documentation":"Thrown by the uploadAvatar mutation when sniffMime() cannot identify the uploaded buffer's MIME type, or when the detected type does not start with 'image/'. This is the first image validation gate, run against the raw bytes (the declared mimetype is only a fallback hint for sniffing). Non-image uploads are rejected before any processing.","triggerScenarios":"Client uploads a file whose magic bytes are not a recognized image format, or whose sniffed type is application/pdf, text/*, etc. Also fires when the buffer is empty or truncated so sniffing fails.","commonSituations":"User selects a PDF, SVG (if not sniffed as image), or text file instead of a photo; upload truncated by a proxy limit; client declares image/png but sends unrelated bytes.","solutions":["Validate the file type on the client before uploading (accept='image/*' and check the actual type).","Ensure the upload is not truncated by body-size limits in the proxy/gateway.","If SVG should be allowed, confirm sniffMime/processImage support it; otherwise restrict the picker to raster formats."],"exampleFix":"// before\n<input type=\"file\" />\n\n// after\n<input type=\"file\" accept=\"image/png,image/jpeg,image/webp,image/gif\" />","handlingStrategy":"validation","validationCode":"const type = await sniffType(file);\nif (!type.startsWith('image/')) { alert('Please choose an image file.'); return; }","typeGuard":"function isImageType(type?: string | null): boolean {\n  return Boolean(type && type.startsWith('image/'));\n}","tryCatchPattern":"try {\n  await uploadAvatar(file);\n} catch (e) {\n  if (e?.code === 'image_format_not_supported') { showFormatError(); return; }\n  throw e;\n}","preventionTips":["Use <input accept=\"image/*\"> and validate the sniffed type client-side.","Reject non-image files before the network round-trip.","Watch for truncated uploads caused by body-size limits."],"tags":["user","avatar","validation","image"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}