Fix Unsupported File Type Errors — MIME Type & Extension Validation
"Unsupported file type", "Invalid file format", "This file type is not allowed" — these errors happen when your application rejects a file that the user thinks should be accepted. Here's how file type detection works and how to fix it.
Common Error Messages
"Unsupported file type. Please upload a PDF, JPG, or PNG.""Invalid file format""Error: MIME type application/octet-stream not allowed""The selected file type is not supported""File validation failed: expected image/*, got application/pdf"
How File Type Detection Works
There are three ways to identify a file's type. Each has trade-offs:
| Method | Checks | Reliable? | Spoofable? |
|--------|--------|-----------|------------|
| File extension | .pdf, .jpg | No | Yes (just rename) |
| MIME type (Content-Type) | image/jpeg | Mostly | Yes (can be set to anything) |
| Magic bytes (file signature) | FF D8 FF = JPEG | Yes | Hard (must modify binary) |
Best practice: Check all three. Extension for user display, MIME for quick filter, magic bytes for security.
Fix 1: Extension-Based Validation
// Basic extension whitelist
const ALLOWED = ['.pdf', '.jpg', '.jpeg', '.png', '.gif', '.webp'];
function validateExtension(filename) {
const ext = '.' + filename.split('.').pop().toLowerCase();
if (!ALLOWED.includes(ext)) {
throw new Error(`Unsupported file type: ${ext}`);
}
}
Problem: Users can rename malware.exe to malware.pdf. Never rely on extension alone.
Fix 2: MIME Type Validation
// Server-side (multer)
const upload = multer({
fileFilter: (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'application/pdf'];
if (allowed.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error(`Unsupported MIME type: ${file.mimetype}`));
}
},
});
// Client-side (HTML)
<input type="file" accept=".pdf,.jpg,.png,image/*" />
Problem: MIME type comes from the browser, which gets it from the OS file association. Rename a .exe to .jpg and many browsers will send image/jpeg.
Fix 3: Magic Bytes Validation (Most Secure)
// Read first bytes of uploaded file and check signature
const FILE_SIGNATURES = {
'image/jpeg': [0xFF, 0xD8, 0xFF],
'image/png': [0x89, 0x50, 0x4E, 0x47],
'application/pdf': [0x25, 0x50, 0x44, 0x46], // %PDF
'image/gif': [0x47, 0x49, 0x46, 0x38], // GIF8
'image/webp': [0x52, 0x49, 0x46, 0x46], // RIFF
'application/zip': [0x50, 0x4B, 0x03, 0x04],
};
function detectFileType(buffer) {
for (const [mime, sig] of Object.entries(FILE_SIGNATURES)) {
if (sig.every((byte, i) => buffer[i] === byte)) {
return mime;
}
}
return null; // Unknown type
}
// Usage in upload handler
app.post('/upload', upload.single('file'), (req, res) => {
const buffer = fs.readFileSync(req.file.path);
const detectedType = detectFileType(buffer);
if (!detectedType) {
fs.unlinkSync(req.file.path);
return res.status(415).json({ error: 'Unrecognized file type' });
}
if (detectedType !== req.file.mimetype) {
console.warn(`MIME mismatch: header=${req.file.mimetype}, actual=${detectedType}`);
}
});
Fix 4: Using file-type Library (Recommended)
import { fileTypeFromBuffer } from 'file-type';
app.post('/upload', upload.single('file'), async (req, res) => {
const buffer = fs.readFileSync(req.file.path);
const type = await fileTypeFromBuffer(buffer);
if (!type) {
return res.status(415).json({ error: 'Could not detect file type' });
}
const allowed = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowed.includes(type.mime)) {
return res.status(415).json({
error: `File type ${type.mime} (${type.ext}) is not allowed`,
});
}
});
Test with Sample Files
Use our MIME Type Lookup Tool to find correct MIME types and magic bytes.
| Test | File | Expected Behavior | |------|------|-------------------| | Valid PDF | sample-1mb.pdf | Accepted | | Valid image | sample-500kb.jpg | Accepted | | MIME mismatch | sample-jpg-as-pdf.pdf | Rejected (JPEG data in .pdf extension) | | Corrupted | sample-corrupt.pdf | Rejected or warning | | Zero byte | sample-zero-byte.bin | Rejected |
Whitelist vs Blacklist
Always whitelist (allow specific types), never blacklist (block specific types):
// WRONG — blacklist (attacker finds new extension)
const BLOCKED = ['.exe', '.bat', '.sh', '.php'];
// RIGHT — whitelist (only known-safe types allowed)
const ALLOWED = ['image/jpeg', 'image/png', 'application/pdf'];
For a complete MIME type reference, use our MIME Types Cheat Sheet.