slopus/happy · error

Unsupported image format

Error message

Unsupported image format

What it means

processImage uses sharp to read an image's metadata and only accepts PNG and JPEG formats. Any other format (webp, gif, heic, avif, svg, etc.) makes it throw 'Unsupported image format'.

Source

Thrown at packages/happy-server/sources/storage/processImage.ts:11

import { thumbhash } from "./thumbhash";

export async function processImage(src: Buffer) {
    const sharp = (await import("sharp")).default;

    // Read image
    let meta = await sharp(src).metadata();
    let width = meta.width!;
    let height = meta.height!;
    if (meta.format !== 'png' && meta.format !== 'jpeg') {
        throw new Error('Unsupported image format');
    }

    // Resize
    let targetWidth = 100;
    let targetHeight = 100;
    if (width > height) {
        targetHeight = Math.round(height * targetWidth / width);
    } else if (height > width) {
        targetWidth = Math.round(width * targetHeight / height);
    }

    // Resize image
    const { data, info } = await sharp(src).resize(targetWidth, targetHeight).ensureAlpha().raw().toBuffer({ resolveWithObject: true });

    // Thumbhash
    const binaryThumbHash = thumbhash(info.width, info.height, data);
    const thumbhashStr = Buffer.from(binaryThumbHash).toString('base64');

View on GitHub (pinned to b824cd0a46)

Solutions

  1. Convert the input image to JPEG or PNG before uploading/processing (e.g. sips -s format jpeg on macOS, ImageMagick convert)
  2. Extend processImage to allow more formats or to convert via sharp(src).toFormat('jpeg') instead of throwing
  3. Sanitize uploads server-side: reject or transcode non-png/jpeg formats early with a clear user-facing message

Example fix

// before
if (meta.format !== 'png' && meta.format !== 'jpeg') {
    throw new Error('Unsupported image format');
}
// after
if (meta.format !== 'png' && meta.format !== 'jpeg') {
    buf = await sharp(src).toFormat('jpeg').toBuffer();
    meta = await sharp(buf).metadata();
}
Defensive patterns

Strategy: validation

Validate before calling

const meta = await sharp(src).metadata();
if (meta.format !== 'png' && meta.format !== 'jpeg') {
  throw new Error(`Unsupported image format: ${meta.format}; convert to png/jpeg first`);
}

Type guard

function isSupportedImage(m: sharp.Metadata): boolean {
  return m.format === 'png' || m.format === 'jpeg';
}

Try / catch

try {
  const out = await processImage(src, dest);
} catch (e) {
  if (e.message === 'Unsupported image format') {
    await sharp(src).toFormat('jpeg').toFile(tempJpeg);
    await processImage(tempJpeg, dest);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling processImage on a source image whose sharp-detected meta.format is neither 'png' nor 'jpeg' — e.g. a WebP screenshot, HEIC iPhone photo, or animated GIF passed to the thumbnailing pipeline.

Common situations: Users upload photos from mobile devices (HEIC), modern web exports (WebP/AVIF), or files with wrong extensions that don't match actual content.

Related errors


AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31). Data as JSON: /api/errors/2a96fe6375f777f9. Report an issue: GitHub.