What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate an image in JavaScript by checking the selected File for presence, an explicit MIME-type allowlist, and a maximum byte count, then use an object URL for an optional preview. Treat those checks as user-interface feedback only: the server must validate the received bytes, decode and safely rewrite accepted images, and choose the stored name and response type.

What JavaScript can—and cannot—validate

A browser file input exposes selected files through input.files. Each File has a name, byte size, and a browser-reported MIME type. Those values let you reject obvious mistakes before an upload starts and explain the policy to the user.

They do not establish that the bytes are a safe image. A user can alter your JavaScript, submit a request without your page, rename a file, or send a forged Content-Type. The receiving service therefore has to repeat validation independently.

Check Browser use Server requirement
Format Compare File.type with an explicit allowlist to give immediate feedback. Detect the actual content, decode it with an image library, and accept only formats the application supports.
Size Compare File.size with your stated limit before upload. Enforce a request and decoded-image limit on the received bytes.
Filename Display it for context, but do not use it as proof. Generate a storage name on the server and derive any extension from detected or rewritten content.
Preview Use an object URL to show what the browser can decode. Decode and rewrite the image before making it available to other users.

MDN describes accept as a hint for the picker, not a validator. OWASP likewise warns that the submitted content type is user-controlled and that signature checks must not be the only defense.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build the browser-side validator

1. Mark up the input and feedback elements

Keep the accepted formats visible in the control and provide an accessible status message. The example policy accepts PNG and JPEG files up to 5 MiB. That number is an application choice, not a JavaScript or web standard.

<label for='image'>Choose an image</label>
<input id='image' type='file' accept='image/png,image/jpeg'>
<p id='message' aria-live='polite'></p>
<img id='preview' alt='Selected image preview' hidden>

2. Check presence, declared type, and bytes

Use a Set so the allowlist is explicit and easy to keep in sync with the server. Clear the input after a rejection so a later upload cannot accidentally reuse a disallowed selection.

const input = document.querySelector('#image');
const message = document.querySelector('#message');
const preview = document.querySelector('#preview');
const allowedTypes = new Set(['image/png', 'image/jpeg']);
const maxBytes = 5 * 1024 * 1024; // 5 MiB: an example application policy
let previewUrl = null;

function clearPreview() {
  preview.hidden = true;
  preview.removeAttribute('src');
  if (previewUrl) {
    URL.revokeObjectURL(previewUrl);
    previewUrl = null;
  }
}

input.addEventListener('change', () => {
  clearPreview();
  const file = input.files?.[0];

  if (!file) {
    message.textContent = 'Choose an image file.';
    return;
  }

  if (!allowedTypes.has(file.type)) {
    message.textContent = 'Choose a PNG or JPEG image.';
    input.value = '';
    return;
  }

  if (file.size > maxBytes) {
    message.textContent = 'The selected file is larger than 5 MiB.';
    input.value = '';
    return;
  }

  previewUrl = URL.createObjectURL(file);
  preview.src = previewUrl;
  preview.hidden = false;
  message.textContent = `${file.name} is ready to preview.`;
});

window.addEventListener('pagehide', clearPreview);

URL.createObjectURL() creates a temporary browser URL without reading the entire file into a JavaScript string. Revoke it when replacing the preview or leaving the page so repeated selections do not retain unnecessary object URLs.

3. Make the policy match the picker

An accept value such as image/* may include formats your processing pipeline cannot decode. Prefer exact MIME types, and optionally list extensions when a platform’s picker benefits from them, for example accept='image/png,image/jpeg,.png,.jpg,.jpeg'. The attribute still remains a hint: users can often override it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Optional checks for dimensions and decodability

A preview can provide useful feedback about whether the browser can decode the selected bytes. You can also inspect dimensions after the image loads and reject images outside your application’s layout policy.

const maxWidth = 8000;
const maxHeight = 8000;

preview.addEventListener('load', () => {
  if (preview.naturalWidth > maxWidth || preview.naturalHeight > maxHeight) {
    message.textContent = `Image dimensions exceed ${maxWidth} × ${maxHeight} pixels.`;
    clearPreview();
    input.value = '';
    return;
  }
  message.textContent = `${preview.naturalWidth} × ${preview.naturalHeight} image ready.`;
});

preview.addEventListener('error', () => {
  clearPreview();
  input.value = '';
  message.textContent = 'This file could not be decoded as an image.';
});

Decodability and dimensions are still client observations. A browser may support a format your server does not, and a malicious file can target weaknesses in any decoder. Repeat these limits while processing the upload on the server.

Validate the upload on the server

Think of the browser code as an early, friendly filter. The server is the security boundary because it receives untrusted bytes regardless of how they were selected.

Use a layered acceptance flow

  1. Limit the request. Set a maximum body size and reject oversized uploads before expensive processing. Apply a decoded-pixel or dimension limit as well if your image library supports it.
  2. Do not trust the submitted Content-Type. It is supplied by the client and can be spoofed. Treat the browser MIME value and multipart header as hints only.
  3. Detect and decode the content. Use a maintained image-processing library to identify the actual format and verify that it can be decoded.
  4. Compare the detected format with your allowlist. If the application accepts PNG and JPEG, reject every other detected type, including formats your browser happens to preview.
  5. Rewrite the image. Decode and re-encode to a known safe representation, stripping data your application does not need. This also prevents the original upload from becoming the served file.
  6. Generate a storage name. Create an application identifier rather than using the user-supplied filename. Store uploads outside executable or template directories when possible.
  7. Serve deliberately. Return the content type that corresponds to the processed output, and apply authorization and download rules appropriate to the files.

OWASP cautions that file-signature checks are useful only as one layer and should not be used on their own. Extension checks, MIME checks, magic bytes, decoder behavior, size limits, and rewriting each cover different failure modes.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep client and server policies synchronized

Define the allowlist and byte limit as shared product policy, then express it separately in the browser and server implementations. If the server accepts only JPEG and PNG, do not advertise a broader image/* picker. If the server changes its limit, update the browser message at the same time; a client rejection should never be the only enforcement.

Uploading after validation

When the browser checks pass, send the original File in a multipart request. Do not send the filename or MIME string as a substitute for the bytes.

const formData = new FormData();
formData.append('image', input.files[0]);

const response = await fetch('/upload', {
  method: 'POST',
  body: formData,
  credentials: 'same-origin'
});

if (!response.ok) {
  message.textContent = 'The server rejected the upload.';
} else {
  message.textContent = 'Upload accepted and processed.';
}

Do not manually set the multipart Content-Type header in this example; the browser adds the boundary. Handle authentication, CSRF protection, rate limits, and authorization according to your application framework.

Multiple files, drag-and-drop, and replacement selections

Multiple files

Use multiple only when the workflow supports it, then iterate over input.files and apply the same checks to every item. Report errors per filename so one bad selection does not look like a server failure for all files.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Drag-and-drop

Drop events expose a DataTransfer.files list. Route those files through the same validation function used by the input’s change event instead of maintaining a second policy.

Replacing or cancelling a selection

A cancelled picker can leave input.files empty. Clear the preview and status when no file is present, and revoke the previous object URL before showing a replacement.

Common failures and fixes

  • The picker shows files that are not accepted. accept is advisory and can be overridden. Keep the client check and enforce the allowlist on the server.
  • file.type is empty or unexpected. Some operating systems and files provide no useful MIME declaration. Give a clear client message, then rely on server-side detection rather than guessing from the extension.
  • A renamed text or executable file passes the extension check. Extensions are labels, not content. Detect, decode, and rewrite on the server.
  • The preview is blank or fires an error. The browser could not decode the bytes, the object URL was revoked too early, or the selected file was cleared. Create a fresh object URL, revoke it only when replacing it, and still reject or investigate the file server-side.
  • Valid large photos are rejected. Check whether the rejection is the byte limit, a decoded-dimension limit, or a reverse-proxy request limit. Raise limits only after considering memory and processing cost, and keep the server and browser messages consistent.
  • The server accepts a file the browser rejected. That is expected when the client is stricter or outdated. The server policy controls acceptance; update the client to improve feedback.
  • Uploads succeed but downloaded files have the wrong type. Derive the output extension and response Content-Type from the detected, rewritten image rather than the submitted filename or header.
  • Repeated previews consume memory. Revoke the previous object URL on every change and on pagehide; avoid converting large files to base64 unless a specific API requires it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability considerations

Client-side size checks are cheap and prevent needless network traffic, but reading dimensions or decoding many large images can still use CPU and memory. For multiple files, validate sequentially or cap the number selected. Keep the preview element constrained with CSS so a very large image does not distort the page.

On the server, reject oversized requests before decoding, use decoder limits against decompression-bomb-style images, and perform rewriting in a worker or queue when processing is expensive. Store only the normalized output if the original is not required for a documented business reason. Log the detected format and rejection reason without logging sensitive image contents.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If your task is obtaining website screenshots rather than accepting user uploads, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one request. You can then run the returned image through the same server-side validation and rewriting pipeline. It removes cookie and consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing result in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Here is the one-call cURL form (see the ScreenshotNeo API documentation for all options):

curl -G 'https://api.screenshotneo.com/v1/shot' -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The equivalent Python request is:

import requests
r = requests.get('https://api.screenshotneo.com/v1/shot', params={'access_key': 'YOUR_API_KEY', 'url': 'https://stripe.com'}, timeout=90)
open('shot.webp', 'wb').write(r.content)

In Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const bytes = Buffer.from(await res.arrayBuffer());
await require('node:fs').promises.writeFile('shot.webp', bytes);

ScreenshotNeo has 1,000 shots per month free with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account and send the returned bytes through your normal image checks.

Security checklist

  • Use an explicit format allowlist in both client and server code.
  • Set request-byte and decoded-image limits appropriate to your application.
  • Treat accept, filename, MIME declarations, previews, and signatures as untrusted signals.
  • Decode and rewrite accepted images with a maintained library.
  • Generate storage names on the server and serve the processed output with the correct content type.
  • Protect the upload endpoint with authentication, authorization, CSRF controls where applicable, and rate limits.
  • Revoke object URLs used for previews.

Frequently Asked Questions

Should I accept WebP, GIF, or SVG with the same code?

Only if your server-side decoder, rewriting policy, and serving rules explicitly support each format. Add each accepted MIME type to both policies; do not broaden the rule to image/* merely because a browser can preview it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can a preview prove that an upload is safe?

No. It proves only that the current browser produced a preview. The server still has to inspect, decode, constrain, and rewrite the received bytes.

Where should uploaded images be stored?

Use server-generated names and a location that cannot execute uploaded content. Expose only the processed output through a response path that applies the intended content type and access controls.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.