nexu-io/open-design · error · Error

--image has unsupported extension "${ext}". Use png, jpg, jp

Error message

--image has unsupported extension "${ext}". Use png, jpg, jpeg, webp, or gif.

What it means

Thrown by resolveProjectImage after reading the file: the extension is not in the tight allowlist of png, jpg, jpeg, webp, gif. The allowlist matches exactly what i2v / image-edit upstream endpoints consume, preventing arbitrary content from being smuggled through as a data URL.

Source

Thrown at apps/daemon/src/media/index.ts:261

  const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
  if (info.size > MAX_IMAGE_BYTES) {
    throw new Error(
      `--image too large (${info.size} bytes; max ${MAX_IMAGE_BYTES}).`,
    );
  }
  const bytes = await readFile(abs);
  const ext = path.extname(abs).toLowerCase();
  // Tight allowlist: only what i2v / image-edit endpoints actually
  // consume. Avoids smuggling arbitrary content through as data URLs.
  const mime = ({
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.webp': 'image/webp',
    '.gif': 'image/gif',
  })[ext];
  if (!mime) {
    throw new Error(
      `--image has unsupported extension "${ext}". Use png, jpg, jpeg, webp, or gif.`,
    );
  }
  return {
    path: rel.trim(),
    abs,
    mime,
    size: bytes.length,
    dataUrl: `data:${mime};base64,${bytes.toString('base64')}`,
  };
}

function clampNumber(value: unknown, allowed: number[]): number | undefined {
  // Accept exact registry values; otherwise snap to the nearest allowed
  // bucket so a hallucinated `Number.MAX_SAFE_INTEGER` can't bill an
  // entire month of credits when real providers plug in.
  if (typeof value !== 'number' || !Number.isFinite(value)) return undefined;
  if (allowed.length === 0) return undefined;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Convert the image to png, jpg/jpeg, webp, or gif before referencing it.
  2. Rename the file to a correct, supported extension that matches its actual content.
  3. For SVGs, rasterize to PNG at the target size first.
  4. For HEIC/AVIF, transcode with an image library to JPEG/WebP.

Example fix

// before
--image assets/logo.svg
// after
sharp('assets/logo.svg').png().toFile('assets/logo.png')
--image assets/logo.png
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);
if (!ALLOWED.has(path.extname(abs).toLowerCase())) throw new Error('unsupported image extension');

Type guard

function isAllowedImageExt(abs: string): boolean {
  return new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']).has(path.extname(abs).toLowerCase());
}

Prevention

When it happens

Trigger: Passing `--image assets/icon.svg`, `.bmp`, `.tiff`, `.heic`, `.avif`, or an extensionless/renamed file; a file whose extension casing differs (handled by lowercasing, so only genuinely unsupported extensions fail).

Common situations: SVG icons (not bitmap-supported); modern formats (AVIF/HEIC) not yet allowlisted; extensionless exports; renamed files where the extension no longer reflects content.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/4e1c39013b0cee73. Report an issue: GitHub.