max-size on file inputs. On change, read each file.size (bytes), compare to your MB limit, and use setCustomValidity. accept is not enforced — check file.type and the extension too. Sum sizes when multiple is set, and mirror every rule on the server.Point any HTML form at one endpoint. 500 free submissions — no card, no time limit — and unlimited forms.
Create free accountHTML has no max-size attribute
People search for input type="file" max size, but the HTML spec does not define a maxsize or max-size attribute on <input type="file">. Attributes like required and accept exist; a byte limit does not. The browser will happily POST a 200 MB file if your backend allows it.
<!-- Valid HTML — but no built-in size cap -->
<input type="file" name="attachment" accept=".pdf,image/*" required />Client-side checks save bandwidth and give instant feedback; they are not security. Treat JavaScript validation as UX and enforce limits again when the bytes hit your server (see file uploads on contact forms).
Check file.size on change with setCustomValidity
File.size is in bytes. Five megabytes is 5 * 1024 * 1024 (5,242,880), not 5 * 1000 * 1000. Listen for change on the input, loop input.files, and call setCustomValidity so native form validation blocks submit:
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
const input = document.querySelector('input[type="file"]');
input.addEventListener("change", () => {
let tooBig = false;
for (const file of input.files) {
if (file.size > MAX_BYTES) tooBig = true;
}
input.setCustomValidity(
tooBig ? "File is larger than 5 MB" : ""
);
});Clear the message when the user picks a valid file, or the field stays invalid after they fix the selection. Pair this with reportValidity() if you validate on submit instead of on every change.
accept filters the picker — it is not enforced
The accept attribute narrows the file dialog on most desktops. Mobile and desktop users can still bypass it. Do not trust file.type alone either — the browser sets it from the extension. Check MIME type and the filename:
const ALLOWED_TYPES = ["application/pdf", "image/png", "image/jpeg"];
function typeOk(file) {
if (ALLOWED_TYPES.includes(file.type)) return true;
const name = file.name.toLowerCase();
return /\.(pdf|png|jpe?g)$/i.test(name);
}
input.addEventListener("change", () => {
const bad = [...input.files].some((f) => !typeOk(f));
input.setCustomValidity(bad ? "Only PDF or PNG/JPEG images" : "");
});Multiple files: add up the total size
With the multiple attribute, input.files is a list. Apply per-file limits and a total cap so one submission cannot exceed your backend quota:
const MAX_PER_FILE = 5 * 1024 * 1024;
const MAX_TOTAL = 15 * 1024 * 1024;
input.addEventListener("change", () => {
let total = 0;
for (const file of input.files) {
if (file.size > MAX_PER_FILE) {
input.setCustomValidity("Each file must be 5 MB or smaller");
return;
}
total += file.size;
}
input.setCustomValidity(
total > MAX_TOTAL ? "Total upload must be 15 MB or less" : ""
);
});Show a readable size (KB / MB)
Error text like "5242880 bytes" confuses users. Format file.size for display:
function formatBytes(bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
}
// "resume.pdf is 8.2 MB (max 5 MB)"
input.setCustomValidity(
`${file.name} is ${formatBytes(file.size)} (max 5 MB)`
);Optional: check image width and height
For avatars or banners you may need minimum dimensions, not just bytes. createImageBitmap reads dimensions without inserting an <img> into the page:
async function imageDimensionsOk(file) {
if (!file.type.startsWith("image/")) return true;
const bitmap = await createImageBitmap(file);
const ok = bitmap.width >= 800 && bitmap.height >= 600;
bitmap.close();
return ok;
}Run this inside your change handler (with await) before you call setCustomValidity. Large photos can be small in bytes but huge in pixels — combine dimension checks with a byte cap.
Always enforce limits on the server
Client checks are trivial to bypass. Your form backend, CDN, and app server each have their own upload limits (413 errors, proxy body size, function payload caps). Reject oversize streams early, sniff file types, and match the rules you showed in the UI. Walkthroughs: add file uploads to a contact form, static site file upload forms, and the <input type="file"> reference. If you need a form that accepts the files once they pass validation, see how to build a file upload form.
// Pseudocode — stop reading once over limit
let received = 0;
for await (const chunk of request.body) {
received += chunk.length;
if (received > MAX_BYTES) {
return new Response("File too large", { status: 413 });
}
}FAQ
How do I limit file upload size in HTML?
HTML has no max-size attribute on input type=file. Use JavaScript: read file.size in bytes on the change event, compare to your limit (for example 5 * 1024 * 1024 for 5 MB), and call setCustomValidity with an error message or an empty string when the file is OK. Always enforce the same limit on the server.
Does the accept attribute stop wrong file types?
No. accept only filters the OS file picker UI. Users can choose All files or rename extensions. Check file.type and the filename extension in JavaScript for early feedback, then verify magic bytes and size on the server.
How do I validate multiple files at once?
When the input has the multiple attribute, loop input.files and sum each file.size. Reject the selection if the total exceeds your cap or if any single file is too large. Show one setCustomValidity message on the input for the whole control.
What is file.size in JavaScript?
file.size is the File object's size in bytes (not kilobytes). Compare it directly to limits like 5 * 1024 * 1024. Use a small helper to display KB or MB in error text so users understand the limit.
References: MDN: File.size and MDN: setCustomValidity.
Need a backend that already caps uploads and stores files safely? splitforms handles multipart POSTs from plain HTML — connect Storage and keep the same setCustomValidity UX on the client.