antiwork/gumroad · warning · ValidationError

Invalid file type.

Error message

Invalid file type.

What it means

First check in validateFile for product thumbnails: FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS) rejects any file whose extension is not on the allowlist, throwing ValidationError with its default message 'Invalid file type.'. This is pure client-side gating before any upload — the file is judged by name extension only, not content.

Source

Thrown at app/javascript/components/ProductEdit/ProductTab/ThumbnailEditor.tsx:32

  eager: true,
  query: "?url",
  import: "default",
});
const nativeTypeThumbnails = Object.fromEntries(
  Object.entries(rawThumbnails).map(([key, value]) => [`./${key.split("/").pop()}`, value]),
);

const MIN_SIDE_DIMENSION = 600;
const MEGABYTE = 1024 * 1024;
const MAX_FILE_SIZE = 5 * MEGABYTE;
export class ValidationError extends Error {
  constructor(message = "Invalid file type.") {
    super(message);
  }
}

const validateFile = async (file: File) => {
  if (!FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS)) throw new ValidationError();

  if (file.size > MAX_FILE_SIZE)
    throw new ValidationError("Could not process your thumbnail, please upload an image with size smaller than 5 MB.");

  const dimensions = await getImageDimensionsFromFile(file).catch(() => null);
  if (!dimensions) throw new ValidationError();
  if (dimensions.height !== dimensions.width) throw new ValidationError("Image must be square.");

  if (dimensions.height < MIN_SIDE_DIMENSION) throw new ValidationError("Image must be at least 600x600px.");
};

export const coverUrlForThumbnail = (covers: AssetPreview[]) =>
  covers.find((cover) => cover.type === "image" || cover.type === "unsplash")?.url ?? null;

export const ThumbnailEditor = ({
  covers,
  thumbnail,
  setThumbnail,

View on GitHub (pinned to afeacbd394)

Solutions

  1. Check ALLOWED_EXTENSIONS in ThumbnailEditor.tsx and export/convert the image to one of them (JPEG/PNG/WebP are the usual set).
  2. Re-save via an image editor or `sips`/ImageMagick: `magick input.tiff output.jpg`.
  3. If a legitimate format is being rejected, extend ALLOWED_EXTENSIONS and mirror any server-side validation.
  4. Set the file picker's accept attribute to the allowed MIME types so invalid files cannot be chosen in the first place.

Example fix

// before
<input type="file" onChange={handleFile} />

// after — the picker itself prevents picking a disallowed type
<input type="file" accept="image/jpeg,image/png,image/webp" onChange={handleFile} />
Defensive patterns

Strategy: validation

Validate before calling

const hasAllowedExtension = (file: File): boolean =>
  FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS);

if (!hasAllowedExtension(file)) {
  showAlert(`Supported formats: ${ALLOWED_EXTENSIONS.join(', ')}.`, 'error');
  return;
}

Type guard

const isValidationError = (e: unknown): e is ValidationError => e instanceof ValidationError;

Try / catch

try {
  await validateFile(file);
} catch (e) {
  if (e instanceof ValidationError) { showAlert(e.message, 'error'); return; } // user-input problem
  throw e; // unexpected — let it propagate
}

Prevention

When it happens

Trigger: Uploading a thumbnail with an extension outside ALLOWED_EXTENSIONS (e.g. .tiff, .heic, .bmp, .pdf when only jpg/png/webp-style entries are allowed); double extensions like photo.jpg.exe; an extensionless filename from a screenshot or drag-and-drop tool.

Common situations: Designers exporting TIFF/HEIC from cameras and design tools; files saved without extensions; users renaming files to force acceptance (caught here); allowlist updated server-side but stale in the client bundle.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/437c8db6d49688c1. Report an issue: GitHub.