Convert Image to Base64 String with JavaScript — FileReader API Guide
Every web application that handles user images needs to solve the same problem: the user selects a file, and the browser needs to do something with it — preview it, upload it to a server, or pass it to a canvas for processing.
The FileReader API with readAsDataURL is the standard way to convert a file to a Base64 string in the browser. It's supported in every modern browser, requires no dependencies, and works with any file type the browser can read.
The Basic Pattern
const fileInput = document.getElementById("image-upload");
fileInput.addEventListener("change", (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const dataUrl = e.target.result;
// dataUrl looks like: "data:image/png;base64,iVBORw0KGgo..."
console.log(dataUrl);
};
reader.onerror = () => {
console.error("Error reading file:", reader.error);
};
reader.readAsDataURL(file);
});
When readAsDataURL completes, reader.result contains a data URI string. This string has two parts separated by a comma:
- Prefix:
data:image/png;base64,— declares the MIME type and encoding - Payload:
iVBORw0KGgo...— the Base64-encoded file data
Getting Just the Base64 Portion
Most APIs expect only the Base64 string without the data:...;base64, prefix:
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
const dataUrl = reader.result;
const base64 = dataUrl.split(",")[1];
resolve(base64);
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
// Usage
const file = event.target.files[0];
const base64 = await fileToBase64(file);
// "iVBORw0KGgo..." — clean Base64, no prefix
Image Preview with Base64
The simplest use case is showing a preview of the selected image before upload:
function previewImage(file, imgElement) {
const reader = new FileReader();
reader.onload = (e) => {
imgElement.src = e.target.result; // data URI works directly in src
imgElement.style.display = "block";
};
reader.readAsDataURL(file);
}
// HTML: <img id="preview" style="display:none" />
// JavaScript:
document.getElementById("image-upload").addEventListener("change", (e) => {
const file = e.target.files[0];
const img = document.getElementById("preview");
previewImage(file, img);
});
The data URI goes directly into the <img> tag's src attribute. No intermediate Blob or object URL needed.
Uploading a Base64 Image to an API
async function uploadImage(file) {
const base64 = await fileToBase64(file);
const payload = {
filename: file.name,
mimeType: file.type,
size: file.size,
content: base64
};
const response = await fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
return response.json();
}
This pattern is common in serverless environments and internal tools where multipart form parsing isn't available. The server receives the Base64 string, decodes it, and stores the file.
Resizing Images Before Base64 Conversion
Uploading a 10 MB smartphone photo as Base64 creates a ~13 MB JSON payload. Resize the image client-side before encoding:
function resizeImage(file, maxWidth, maxHeight) {
return new Promise((resolve) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
let { width, height } = img;
if (width > maxWidth) {
height = Math.round(height * (maxWidth / width));
width = maxWidth;
}
if (height > maxHeight) {
width = Math.round(width * (maxHeight / height));
height = maxHeight;
}
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
// Convert resized canvas to Base64 (JPEG at 80% quality)
const base64 = canvas.toDataURL("image/jpeg", 0.8).split(",")[1];
URL.revokeObjectURL(url);
resolve(base64);
};
img.src = url;
});
}
// Usage
const base64 = await resizeImage(file, 1200, 1200);
This pattern reduces the upload payload dramatically. A 4000×3000 pixel photo (roughly 3-5 MB as JPEG) becomes a 1200×900 image (150-300 KB) — a 90% reduction before Base64 adds its 33% overhead.
Multiple Files
async function filesToBase64(files) {
const promises = Array.from(files).map((file) => fileToBase64(file));
return Promise.all(promises);
}
// Multiple file upload
document.getElementById("multi-upload").addEventListener("change", async (e) => {
const files = e.target.files; // FileList
const base64Array = await filesToBase64(files);
const payload = {
images: base64Array.map((base64, i) => ({
filename: files[i].name,
mimeType: files[i].type,
content: base64
}))
};
// POST payload to API...
});
Note: FileReader processes each file sequentially on the main thread. For large batches, consider chunking or using a Web Worker.
Validation Before Conversion
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE_MB = 5;
const MAX_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024;
function validateImageFile(file) {
if (!file) {
return { valid: false, error: "No file selected" };
}
if (!ALLOWED_TYPES.includes(file.type)) {
return { valid: false, error: `Unsupported type: ${file.type}. Allowed: JPEG, PNG, WebP` };
}
if (file.size > MAX_SIZE_BYTES) {
return { valid: false, error: `File too large: ${(file.size / 1024 / 1024).toFixed(1)} MB. Max: ${MAX_SIZE_MB} MB` };
}
return { valid: true };
}
// Usage
document.getElementById("upload").addEventListener("change", async (e) => {
const file = e.target.files[0];
const { valid, error } = validateImageFile(file);
if (!valid) {
showError(error);
return;
}
const base64 = await fileToBase64(file);
// Proceed with upload...
});
FileReader Events Reference
const reader = new FileReader();
reader.onloadstart = () => console.log("Reading started");
reader.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
console.log(`${percent}% complete`);
}
};
reader.onload = () => console.log("Reading complete");
reader.onerror = () => console.error("Error:", reader.error);
reader.onabort = () => console.log("Reading aborted");
reader.readAsDataURL(file);
// To cancel mid-read:
// reader.abort();
The onprogress event is useful for showing upload progress when reading large files, though readAsDataURL typically completes in a single chunk for most images.
FAQ
Can I convert a remote image URL to Base64?
Yes, but you need to bypass CORS restrictions. Fetch the image as a Blob, then use FileReader:
async function urlToBase64(imageUrl) {
const response = await fetch(imageUrl);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(",")[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
The remote server must set Access-Control-Allow-Origin headers, or the fetch will fail.
What's the maximum file size FileReader can handle?
readAsDataURL loads the entire file into memory as a Base64 string. For a 100 MB file, that's roughly 133 MB of JavaScript string. This can crash low-memory devices (mobile phones with 2-4 GB RAM). For large files, use readAsArrayBuffer and upload via FormData with multipart encoding instead.
Is FileReader supported in all browsers?
Yes. FileReader is supported in Chrome 7+, Firefox 3.6+, Safari 6+, Edge 12+, and IE 10+. For IE 9 and below, fall back to a server-side upload or use Flash-based alternatives (though this is rarely necessary today).
How do I convert Base64 back to a file for re-upload?
Use the base64ToBlob pattern: decode with atob(), create a Uint8Array, wrap in a Blob, and optionally create a File object. The full pattern is in Creating a Blob from a Base64 String.
What's the performance difference between Base64 and object URLs for preview?
Object URLs create a short-lived reference to the original file without encoding it — they're nearly instant and use less memory. For image previews, URL.createObjectURL(file) is faster and more memory-efficient than Base64. Use Base64 only when you need the encoded string for upload or storage.
For quick conversion of images to Base64 strings without writing code, the Base64 Encoder & Decoder tool supports file uploads and generates clean Base64 output ready for API payloads. If you're embedding images directly in HTML, see How to Display Base64 Images in HTML for performance guidelines and best practices.