How to Upload Images with JavaScript
Upload an image from a browser with a native form or JavaScript fetch and FormData. Includes complete examples, error handling, CORS guidance, and server-side validation notes.
To upload an image with JavaScript, let the user choose a file with <input type="file">, read the selected File from the input’s files list, and send it to an application endpoint as FormData with fetch(). Do not set the multipart Content-Type header yourself: the browser adds the boundary the server needs to parse the request. Your server still has to receive, validate, and store or process the file.
For the simplest upload, use a normal HTML form with method="post" and enctype="multipart/form-data". Use JavaScript-managed submission when you want to keep the page in place and show a status message. The browser-side pattern follows MDN’s form submission guidance and its FormData examples.
1. Choose the upload approach
| Approach | Use it when | Trade-off |
|---|---|---|
| Native HTML form | A page navigation or server-rendered response is fine and you want minimal JavaScript. | The browser submits the form and handles the navigation; in-page progress and status behavior are limited. |
JavaScript with fetch() |
The page should remain in place and you want to handle the response or update the interface. | You must write submission and error-handling code. The server still needs to parse the multipart request. |
2. Add a file input
The browser’s file picker gives your page a File object through input.files. Do not try to get the image bytes from the input’s string value. Give the input a name: a form or FormData submission uses that name as the field key the server receives.
<form id="image-form">
<label for="image">Choose an image</label>
<input
id="image"
name="image"
type="file"
accept="image/*"
required
>
<button type="submit">Upload</button>
<p id="status" role="status"></p>
</form>
accept="image/*" guides the picker toward image files; you can use narrower hints such as accept=".jpg,.jpeg,.png". It is not a security check or a guarantee that the selected content is actually an image. Validate the file on the server. See MDN’s documentation for the file input and the accept attribute.
3. Upload with JavaScript and fetch
This complete browser-side example intercepts form submission, creates FormData from the form, sends it to /uploads, checks the HTTP status, and reports success or failure. Replace /uploads with your application’s actual route, and make sure its multipart parser expects a file field named image.
<form id="image-form">
<label for="image">Choose an image</label>
<input id="image" name="image" type="file" accept="image/*" required>
<button type="submit">Upload</button>
<p id="status" role="status"></p>
</form>
<script>
const form = document.querySelector("#image-form");
const status = document.querySelector("#status");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const fileInput = form.elements.image;
const file = fileInput.files[0];
if (!file) {
status.textContent = "Choose an image first.";
return;
}
const formData = new FormData(form);
try {
const response = await fetch("/uploads", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
status.textContent = "Upload complete.";
} catch (error) {
status.textContent = "Upload failed. Please try again.";
console.error(error);
}
});
</script>
fetch() resolves to a Response even when the server responds with an HTTP error such as 400 or 500. Check response.ok or response.status; a fulfilled promise alone does not mean the upload succeeded. If your endpoint returns JSON, read it with await response.json(). If it returns no JSON, do not parse it as JSON.
Send a selected file without building FormData from a form
If you do not have a form, append the selected file explicitly. The field name must match what your server expects:
<label for="image-file">Choose an image</label>
<input id="image-file" type="file" accept="image/*">
<button id="upload-button" type="button">Upload</button>
<p id="upload-status" role="status"></p>
<script>
const input = document.querySelector("#image-file");
const button = document.querySelector("#upload-button");
const status = document.querySelector("#upload-status");
button.addEventListener("click", async () => {
const file = input.files[0];
if (!file) {
status.textContent = "Choose an image first.";
return;
}
const formData = new FormData();
formData.append("image", file, file.name);
try {
const response = await fetch("/uploads", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
status.textContent = "Upload complete.";
} catch (error) {
status.textContent = "Upload failed. Please try again.";
console.error(error);
}
});
</script>
4. Submit with a native HTML form
Use a regular form when a full-page request and response suit the application. The browser submits the selected file as multipart form data:
<form action="/uploads" method="post" enctype="multipart/form-data">
<label for="image">Choose an image</label>
<input id="image" name="image" type="file" accept="image/*" required>
<button type="submit">Upload</button>
</form>
The endpoint must accept POST and parse multipart/form-data. Add ordinary named form controls if the server needs other fields alongside the image. MDN explains the form’s method and encoding requirements.
5. Upload multiple images
Add multiple only if the feature and endpoint support more than one image. The input’s files property is a FileList, so iterate over it. Decide whether the server expects repeated fields with the same name or a different field naming scheme; align the client with the server parser.
<input id="images" name="images" type="file" accept="image/*" multiple>
<button id="upload-many" type="button">Upload images</button>
<script>
const input = document.querySelector("#images");
document.querySelector("#upload-many").addEventListener("click", async () => {
const formData = new FormData();
for (const file of input.files) {
formData.append("images", file, file.name);
}
const response = await fetch("/uploads", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
});
</script>
For a multi-file interface, also handle the empty selection and show which files were accepted or rejected by the server. The server’s request-size and per-file limits govern what can be uploaded; there is no universal size limit established by the browser APIs.
6. Configure the server boundary
JavaScript only selects and transmits the file. The application endpoint must parse the multipart body, enforce its own size and file validation rules, and decide how to store or process the image. Treat uploaded content and client-supplied metadata as untrusted. A filename, extension, or browser-reported MIME type is useful for interface hints, but must not be the server’s sole security check.
- Make the input’s
namematch the field name your multipart parser reads. - Enforce request and file size limits in the application and deployment.
- Validate that the received content meets the application’s image requirements.
- Apply the authentication, authorization, storage, and serving rules required by your application.
- Return a meaningful HTTP status and response body so the client can show a useful result.
These are boundary checks for an upload flow, not a complete secure-upload specification. Follow the requirements of your server framework and deployment. MDN likewise describes server-side handling and validation as part of sending form data.
7. Handle cross-origin upload endpoints
If the page and upload endpoint have different origins, the endpoint must configure CORS for the browser to let JavaScript access the response. Depending on the request and headers, the browser may send a preflight request. Configure the server for the actual origin, method, and headers your application uses.
Do not use mode: "no-cors" to work around a CORS error. It produces an opaque response whose body and headers JavaScript cannot inspect, so the page cannot reliably determine whether the upload succeeded. See MDN’s Fetch API guide.
8. Improve the upload experience
Show useful status and errors
Use a status region such as <p role="status"> to announce progress or completion. Distinguish a missing selection, a server rejection, and a network or CORS failure where the application can identify them. Avoid telling users an upload completed until the server has returned a successful response.
Show a local preview when needed
A preview can be made from the selected file without uploading it. This is optional UI behavior; it does not validate the file or prove that the server will accept it. Revoke object URLs when they are no longer needed:
const input = document.querySelector("#image");
const preview = document.querySelector("#preview");
let previewUrl;
input.addEventListener("change", () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
const file = input.files[0];
if (!file) {
preview.removeAttribute("src");
return;
}
previewUrl = URL.createObjectURL(file);
preview.src = previewUrl;
});
9. Performance, reliability, and cost
- Request size: Large files take longer to transmit and may exceed limits set by the endpoint, proxy, or hosting environment. Check the actual limits in your stack and provide clear feedback when a server rejects a file.
- Page responsiveness:
fetch()keeps the page available for interface updates, but it does not make transmission instantaneous. Prevent accidental duplicate submissions while a request is in progress if duplicate uploads would be a problem. - Reliability: Network failures and server errors are possible. Check the response, show failure clearly, and make retries deliberate. If retrying could create duplicate records, design the endpoint to handle that case.
- Browser memory: Avoid reading or copying large files into JavaScript strings just to upload them. Pass the
FilethroughFormDataas shown. - Cost: The browser APIs themselves do not define a hosting price. Storage, bandwidth, image processing, and request handling costs depend on the application’s infrastructure and policies; check those providers’ terms and usage.
10. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The server receives no file. | The input has no name, the client field name differs from the parser’s expected key, or the endpoint is not parsing multipart data. |
Set a matching name, send FormData, and configure the endpoint’s multipart parser. |
| The server reports malformed multipart data. | The request’s Content-Type was manually set to multipart/form-data without the browser-generated boundary. |
Remove that header when sending FormData; let the browser set it. |
| The UI says success after a server error. | The code treats a resolved fetch() promise as success without checking the HTTP status. |
Check response.ok and handle non-success statuses. |
| The upload request fails with a CORS error. | The server does not allow the page’s origin, method, or required headers, or the browser’s preflight is not handled. | Configure CORS on the endpoint and handle any required preflight. Do not switch to no-cors if you need to read the result. |
| The picker shows unexpected file types. | accept is only a picker hint and can be overridden. |
Validate the received content on the server and reject files outside the application’s supported types. |
| The request is rejected for size. | An application, proxy, or hosting limit was exceeded. | Check the configured limits, provide a useful message, and adjust limits only if the application can safely support the larger request. |
| A multiple-file upload sends only one file. | The code reads only files[0], or the endpoint expects a different multipart field structure. |
Iterate through input.files and agree on the repeated field names or structure with the server. |
response.json() throws. |
The endpoint returned an empty body, HTML, or another non-JSON response. | Read JSON only when the endpoint actually returns JSON; otherwise use the appropriate response handling. |
11. Or skip the browser setup
If your task is to capture a website image rather than upload a user-selected image, ScreenshotNeo is a website screenshot API and MCP server. One GET request with a URL returns a PNG, JPEG, WebP, or PDF. Use the ScreenshotNeo API documentation for options and setup.
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}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
12. FAQ
Can JavaScript upload an image without a server?
JavaScript in the browser can select and send a file, but an endpoint must receive and handle it. The browser does not persist the upload for your application.
Should I use FormData or convert the image to Base64?
For a normal file upload, use FormData and send the File. Base64 conversion is not needed for this multipart pattern.
Can I trust the file’s MIME type or filename?
No. They are client-provided metadata. Use them for interface hints only; validate the received content according to the server’s requirements.
Does fetch upload the file in the background?
fetch() sends a request from the page and lets JavaScript handle its response. It does not remove the need for a server endpoint or guarantee that the upload succeeds.


