How to Show an Image Preview Before Uploading With jQuery
Preview a selected image instantly with jQuery using object URLs, with validation, cleanup, multiple files, accessibility, and troubleshooting.
You can show an image before uploading it by listening for the file input’s change event, reading the first item from input.files, creating a local object URL with URL.createObjectURL(file), and assigning that URL to an <img>. The browser displays the preview locally; no network request is made until your upload code sends the file.
This example uses jQuery’s .on("change", handler). The older .change() shorthand is deprecated in jQuery 3.3. See the MDN file API guide and jQuery .on() documentation.
1. Minimal working preview
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Image preview</title>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<style>
#imagePreview {
display: block;
max-width: 320px;
max-height: 240px;
margin-top: 1rem;
object-fit: contain;
}
</style>
</head>
<body>
<form id="imageForm">
<label for="imageInput">Choose an image</label>
<input id="imageInput" type="file" accept="image/*">
<img id="imagePreview" alt="Selected image preview" hidden>
<p id="imageStatus" role="status"></p>
<button type="submit">Upload</button>
</form>
<script>
let previewUrl;
$("#imageInput").on("change", function () {
const file = this.files && this.files[0];
const preview = document.getElementById("imagePreview");
const status = document.getElementById("imageStatus");
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
previewUrl = undefined;
}
if (!file || !file.type.startsWith("image/")) {
preview.removeAttribute("src");
preview.hidden = true;
status.textContent = file ? "Please choose an image file." : "";
return;
}
previewUrl = URL.createObjectURL(file);
preview.src = previewUrl;
preview.hidden = false;
status.textContent = `${file.name} selected.`;
});
window.addEventListener("beforeunload", function () {
if (previewUrl) URL.revokeObjectURL(previewUrl);
});
</script>
</body>
</html>
accept="image/*" guides the file picker, while the MIME-type check handles unsuitable selections. Neither is a security boundary: validate the file again on the server before storing or processing it.
2. How the flow works
- The user selects a file.
- The browser fires
change. this.files[0]returns aFileobject.URL.createObjectURL(file)creates a temporary local URL.- The image element loads that URL and becomes visible.
- Your upload request can later send the same
FilewithFormData.
The object URL points to browser-managed data; it does not upload the file. Revoke old URLs when replacing a selection and when the component is destroyed.
3. FileReader alternative
Use FileReader.readAsDataURL() when the surrounding code specifically needs an encoded data URL. The reader is asynchronous and exposes the result after its load event.
$("#imageInput").on("change", function () {
const file = this.files && this.files[0];
const preview = document.getElementById("imagePreview");
const status = document.getElementById("imageStatus");
if (!file || !file.type.startsWith("image/")) {
preview.removeAttribute("src");
preview.hidden = true;
status.textContent = "Please choose an image file.";
return;
}
const reader = new FileReader();
reader.addEventListener("load", function () {
preview.src = reader.result;
preview.hidden = false;
status.textContent = `${file.name} selected.`;
});
reader.addEventListener("error", function () {
preview.removeAttribute("src");
preview.hidden = true;
status.textContent = "The file could not be read.";
});
reader.readAsDataURL(file);
});
| Approach | Result | Lifecycle work | Use when |
|---|---|---|---|
| Object URL | Temporary local URL | Revoke with URL.revokeObjectURL() |
You only need an image source for the preview |
| FileReader | Encoded data URL | Wait for load and handle error |
Your code needs the encoded data itself |
4. Show several selected images
<input id="images" type="file" accept="image/*" multiple>
<div id="previews" aria-live="polite"></div>
<p id="multiStatus" role="status"></p>
<script>
const previewUrls = [];
$("#images").on("change", function () {
const container = document.getElementById("previews");
const status = document.getElementById("multiStatus");
previewUrls.splice(0).forEach(URL.revokeObjectURL);
container.replaceChildren();
const files = Array.from(this.files || []);
const validFiles = files.filter(file => file.type.startsWith("image/"));
validFiles.forEach(file => {
const figure = document.createElement("figure");
const image = document.createElement("img");
const caption = document.createElement("figcaption");
const url = URL.createObjectURL(file);
previewUrls.push(url);
image.src = url;
image.alt = `Preview of ${file.name}`;
image.width = 180;
caption.textContent = file.name;
figure.append(image, caption);
container.append(figure);
});
status.textContent = `${validFiles.length} image${validFiles.length === 1 ? "" : "s"} ready.`;
if (validFiles.length !== files.length) {
status.textContent += " Non-image files were ignored.";
}
});
window.addEventListener("beforeunload", () => {
previewUrls.forEach(URL.revokeObjectURL);
});
</script>
Use textContent for filenames. Do not put a user-controlled filename into a string passed to jQuery’s .html(), which parses the string as HTML.
5. Add upload after preview
$("#imageForm").on("submit", async function (event) {
event.preventDefault();
const file = document.getElementById("imageInput").files[0];
if (!file || !file.type.startsWith("image/")) return;
const body = new FormData();
body.append("image", file, file.name);
const response = await fetch("/upload", {
method: "POST",
body
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
});
Previewing and uploading are separate operations. The preview happens locally; the fetch call is the point at which bytes leave the browser.
6. Validation and edge cases
- No selection: Browsers can fire a change event after clearing the control. Hide the preview and clear its source.
- Wrong type: Check
file.typefor user feedback, then perform authoritative server-side validation. - Replacing a file: Revoke the old object URL before creating the new one.
- Large images: A preview may consume substantial memory. Display a constrained element and consider resizing only if your application needs it.
- Same file selected twice: Some browsers do not fire
changeif the value did not change. Clearinput.valueafter processing if your UX needs repeated selection of the same file. - Component removal: Revoke URLs when a modal, view, or component is disposed.
- Accessibility: Keep a visible label, meaningful image
alttext, and a status region for errors and selection changes. - Security: Do not trust client-side MIME checks, filenames, dimensions, or extensions for storage decisions.
7. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Nothing happens | The handler runs before jQuery or targets the wrong ID. | Load jQuery first, verify the selector, and bind with .on("change", ...). |
| Broken image icon | The selected file is not an image or the URL was revoked too early. | Check file.type and revoke only after replacing or disposing the preview. |
| Preview remains from the previous file | The old src and visibility state were not cleared. |
Remove src, set hidden = true, then process the new file. |
| Filename appears as markup | A filename was inserted with .html(). |
Use textContent or jQuery .text(). |
| Upload succeeds but server rejects it | Client checks are only hints and can be bypassed. | Validate type, size, dimensions, and content on the server. |
| Memory grows after many selections | Object URLs were never released. | Track each URL and call URL.revokeObjectURL() when no longer needed. |
8. Performance, reliability, and cost
Object URLs avoid copying the image into a JavaScript string, while FileReader produces encoded data that can be useful elsewhere. The supplied sources do not establish a universal speed winner, so choose based on whether you need a URL or encoded data. Limit preview dimensions with CSS, avoid generating unnecessary previews, and release URLs promptly. A local preview itself has no network or API cost; upload costs and server processing begin only when you send the file.
9. Or skip the browser setup
If your goal is to capture a preview page or any public URL as an image, ScreenshotNeo provides a single request for PNG, JPEG, WebP, or PDF. Read 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
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing result. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
10. FAQ
Does selecting a file upload it?
No. Selection only exposes a local File object. Upload occurs when your code sends it.
Should I use object URLs or FileReader?
Use an object URL for a straightforward preview. Use FileReader when another part of your code needs a data URL.
Can I rely on accept="image/*" for security?
No. It guides the picker. Validate the actual upload on the server.
Why should URLs be revoked?
Revocation releases browser resources associated with temporary object URLs, especially when users replace files repeatedly.


