How to check which file types a file input accepts
Read input.accept in the Console and split it into extensions and MIME types. Then attach a file that matches neither and read input.files[0] and input.validity.valid. On the form below, a .txt file attached cleanly, the input stayed valid, and the endpoint stored it.
Why check this
Run this when a file input is added or its accept list changes, and again before any release that claims the upload is restricted. The attribute reads like a rule and behaves like a suggestion.
The failure it prevents is an endpoint that stores whatever arrives because the ticket said the input only takes images. accept filters what the operating system picker offers. It does not bar a file from the control, it raises no validation error, and it has no effect at all on a request built by a script or by curl. The type check has to exist on the server, and the only way to know whether it does is to send a file that breaks the rule.
Prerequisites
- Node 22 and Chrome. The form and the upload endpoint run on localhost. No third party receives the test files.
- The
form-lab.mjsserver from How to test form validation messages, started withnode form-lab.mjs 9317. Its file input declaresaccept="image/png,image/jpeg,.pdf"and posts to/upload, which stores anything. - Six fixture files in one directory. Two of them lie about their contents.
node -e "const fs=require('fs');
const png=Buffer.from('89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000a49444154789c6360000002000100ffff03000006000557bfabd40000000049454e44ae426082','hex');
fs.writeFileSync('shot.png',png);
fs.writeFileSync('report.pdf','%PDF-1.4\n1 0 obj<</Type/Catalog>>endobj\n%%EOF\n');
fs.writeFileSync('notes.txt','plain text, not an image and not a pdf\n');
fs.writeFileSync('invoice.pdf',png);
fs.writeFileSync('logo.png','%PDF-1.4\nthis is a pdf wearing a png name\n%%EOF\n');
fs.writeFileSync('archive',Buffer.from('504b0304140000000800','hex'));"
- The accept attribute reference states that it is a filter for the file picker.
- The browser figures are one capture, Chrome 152.0.7977.76, on 2026-09-11. Files were attached with the DevTools protocol, which is the same path a test framework uses.
Steps
- Step 1.
Open the page and read what the input declares.
const el = document.getElementById('doc'), t = el.accept.split(',').map((x) => x.trim()); `accept="${el.accept}" extensions: ${t.filter((x) => x.startsWith('.')).join(' ')} mime types: ${t.filter((x) => !x.startsWith('.')).join(' ')} required=${el.required} multiple=${el.multiple} files.length=${el.files.length}`;accept="image/png,image/jpeg,.pdf" extensions: .pdf mime types: image/png image/jpeg required=true multiple=false files.length=0Two kinds of token in one list.
.pdfmatches on the file name,image/pngmatches on the type the browser assigned. They are compared differently, and a file can match one and fail the other. - Step 2.
Attach each fixture in turn and read what the control reports. Use
elementHandle.uploadFile()in your automation, which is the same call the DevTools protocol exposes.const el = document.getElementById('doc'), file = el.files[0]; const tokens = el.accept.split(',').map((x) => x.trim().toLowerCase()); const byExt = tokens.some((t) => t.startsWith('.') && file.name.toLowerCase().endsWith(t)); const byMime = tokens.some((t) => !t.startsWith('.') && (t === file.type || (t.endsWith('/*') && file.type.startsWith(t.slice(0, -1))))); `name=${file.name} size=${file.size} type="${file.type}" matches accept by extension=${byExt} by mime=${byMime} input.validity.valid=${el.validity.valid} validationMessage="${el.validationMessage}" form.checkValidity()=${document.getElementById('docs').checkValidity()}`;name=shot.png size=75 type="image/png" matches accept by extension=false by mime=true input.validity.valid=true validationMessage="" form.checkValidity()=true name=notes.txt size=39 type="text/plain" matches accept by extension=false by mime=false input.validity.valid=true validationMessage="" form.checkValidity()=true name=invoice.pdf size=75 type="application/pdf" matches accept by extension=true by mime=false input.validity.valid=true validationMessage="" form.checkValidity()=true name=logo.png size=48 type="image/png" matches accept by extension=false by mime=true input.validity.valid=true validationMessage="" form.checkValidity()=true name=archive size=10 type="" matches accept by extension=false by mime=false input.validity.valid=true validationMessage="" form.checkValidity()=trueinput.validity.valid=trueon every file,notes.txtandarchiveincluded. Neither matches a single token inaccept, and constraint validation does not care. The file with no extension got an emptytype, so a MIME comparison on that row compares against nothing. - Step 3.
Read the first bytes of the two files whose names lie about their contents.
const f = document.getElementById('doc').files[0]; const b = new Uint8Array(await f.slice(0, 8).arrayBuffer()); `${f.name}: declared type "${f.type}", first bytes ${[...b].map((x) => x.toString(16).padStart(2, '0')).join(' ')}`;invoice.pdf: declared type "application/pdf", first bytes 89 50 4e 47 0d 0a 1a 0a logo.png: declared type "image/png", first bytes 25 50 44 46 2d 31 2e 3489 50 4e 47is the PNG signature and25 50 44 46is%PDF. Both files are the opposite of their names, andfile.typefollowed the extension in both cases. The browser did not open either file. - Step 4.
Submit the disallowed
.txtthrough the form itself, with no script bypass.document.getElementById('docs').querySelector('button').click();{"stored":true,"filename":"notes.txt","fileBytes":39,"requestBytes":222,"truncated":false}The form navigated and the server answered 200. Nothing in the browser stood in the way.
- Step 5.
Post the same file straight to the endpoint, naming a type that appears nowhere in
accept.curl -s -i -F 'file=@notes.txt;type=text/plain' http://localhost:9317/uploadHTTP/1.1 200 OK content-type: application/json … {"stored":true,"filename":"notes.txt","fileBytes":39,"requestBytes":238,"truncated":false}The type in a multipart part is a string the client chose. Treat it as user input.
- Step 6.
Check the file the way the server has to: by its contents.
head -c 8 invoice.pdf | od -An -tx189 50 4e 47 0d 0a 1a 0aA file called
invoice.pdf, accepted by the.pdftoken, holding PNG bytes. If your endpoint keys anything off the extension or off the declared type, this is the file that proves it.
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| valid=true for a file outside accept | Working as specified. accept is not a constraint | Look for the check on the server, and file a defect if there is none. |
| file.type is an empty string | The extension is unknown to the browser | Any MIME comparison silently passes or fails. Match on bytes. |
| file.type disagrees with the first bytes | The type came from the extension, not the contents | Send that file to the endpoint and read what it stored. |
| The endpoint answers 200 to a disallowed type | The upload has no type check | Raise it against the endpoint. The attribute cannot fix it. |
Common mistakes
What to check next
- How to test form validation messages: the same form, and the rules that do raise a validity flag.
- How to test file upload size limit: the other property of an upload that the browser will not enforce for you.
- How to test API with invalid input: the shape of the response the endpoint owes you for a rejected file.
- How to check content-type of API response: reading the type on the way back, where it is the server making the claim.
- How to check console errors on a website: catches an upload widget that fails quietly after attaching a file it cannot read.
FAQ
Does the accept attribute validate the file type?
No. It sets the default filter in the file picker. The HTML specification gives it no role in constraint validation, and the capture above confirms it: every disallowed file left validity.valid at true and reached the server.
What is the difference between an extension token and a MIME token?
An extension token such as .pdf is compared against the end of the file name. A MIME token such as image/png is compared against file.type, which Chrome derives from that same extension. image/* matches any type in the group.
How do I check a file MIME type before upload?
Read the first bytes with file.slice(0, 8).arrayBuffer() and compare them against the signature for the format. file.type is not evidence, as step 3 shows.
Can a test rely on the picker refusing a file?
No, and the picker cannot be driven from an automated test in the first place. Attach the file programmatically and assert on the response from the endpoint.
What should the server do with a mismatch?
Answer a 4xx with a body naming the field and the accepted types, and store nothing. Silent acceptance and a 500 are both defects.
Verified
Verified by Maks VernyChrome 152.0.7977.76curl 8.21.0Node 22.23.2
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate10 minpublished updated Maks Verny