Logo
Back to Blog
Development August 8, 2026 9 min read

Convert an Image to Base64 in JavaScript: All Three Methods

O

OmniWebKit Team

Frontend Engineering

Share:
Article Cover Image

Three lines of code, and it still goes wrong. You call a function, get undefined, and lose an hour. Converting an image to Base64 in JavaScript looks trivial until the async timing or a CORS header bites you.

There are exactly three approaches. This covers all of them, plus the traps that only show up in production.

How to Convert an Image to Base64 in JavaScript

Pick your method by where the image comes from:

  • A file input or drag-and-drop — use FileReader.
  • An image already on the page — use a canvas.
  • A remote URL — use fetch, then FileReader.

Only one of those gives you the original bytes. We will come back to why that matters.

If you just need a string right now and not the code, our image to Base64 converter does it in the browser with no upload.

Method 1: FileReader and readAsDataURL

This is the one you want most of the time. It reads the file straight from disk and encodes it without touching a single pixel.

const input = document.querySelector('input[type=file]');

input.addEventListener('change', (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();

  reader.onload = () => {
    console.log(reader.result); // data:image/png;base64,iVBORw0...
  };

  reader.readAsDataURL(file);
});

The whole thing hinges on onload. The read is asynchronous, so anything outside that handler runs before the result exists.

In modern code we wrap it in a Promise and forget it was ever callback-based:

const toBase64 = (file) => new Promise((resolve, reject) => {
  const reader = new FileReader();
  reader.onload = () => resolve(reader.result);
  reader.onerror = reject;
  reader.readAsDataURL(file);
});

const dataUrl = await toBase64(file);

One caveat we hit on a client project: for files over roughly 10 MB, this locks the main thread while it encodes. Move it into a Web Worker if your users upload large images. FileReader works there too.

Method 2: Reading Pixels with canvas.toDataURL

Use this when the image is already rendered and you want to change format or size on the way out.

function imageToBase64(img, type = 'image/png', quality = 0.92) {
  const canvas = document.createElement('canvas');
  canvas.width = img.naturalWidth;
  canvas.height = img.naturalHeight;
  canvas.getContext('2d').drawImage(img, 0, 0);
  return canvas.toDataURL(type, quality);
}

Powerful, and lossy in ways people do not expect. The canvas decodes your image to raw pixels and compresses it again from scratch.

Three things disappear in that round trip:

  • EXIF metadata. Camera settings, timestamps, GPS coordinates — all gone.
  • The ICC colour profile. Wide-gamut photos can come back visibly flatter.
  • Bit depth control. Output is always 32-bit RGBA, so an efficient 8-bit PNG gets larger.

That metadata loss is genuinely useful when you are scrubbing user uploads. It is a disaster when you needed the orientation flag. We have shipped both bugs.

The PNG to Base64 encoder deliberately avoids canvas for exactly this reason.

Method 3: Fetching a Remote Image as a Blob

For an image on another server, fetch it, get a Blob, then hand that to FileReader:

async function urlToBase64(url) {
  const res = await fetch(url);
  const blob = await res.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

This keeps the original bytes, unlike the canvas route. It also fails instantly without the right CORS headers.

The CORS Trap That Breaks Two of These

Here is the error that sends developers in circles:

Uncaught DOMException: Failed to execute 'toDataURL' on
'HTMLCanvasElement': Tainted canvases may not be exported.

Drawing a cross-origin image onto a canvas taints it. The browser then refuses to let any script read the pixels back, because otherwise any page could silently exfiltrate images you are logged in to see.

Two things have to line up:

  1. Set img.crossOrigin = 'anonymous' before assigning src. After is too late.
  2. The remote server must return Access-Control-Allow-Origin.

The second is not yours to fix. If the server does not cooperate, no client-side trick will help — you need a server-side proxy. Our CORS tester will tell you in seconds whether the headers are actually there.

Which Method Should You Pick?

SituationUseBytes preserved?
File input or drop zoneFileReaderYes
Resize or change formatCanvasNo
Strip EXIF on purposeCanvasNo, deliberately
Remote URL, CORS allowedfetch + FileReaderYes
Remote URL, CORS blockedServer-side proxyYes
Node.jsBufferYes

On the server it is one line, and none of the browser complexity applies:

const b64 = require('fs').readFileSync('logo.png').toString('base64');

Mistakes That Have Cost Us Hours

Five we keep seeing in code review:

  • Slicing the prefix by length. data:image/png;base64, and data:image/jpeg;base64, are different lengths. Split on the first comma.
  • Calling btoa on text. It throws on anything outside Latin-1. Encode through TextEncoder first — our Base64 encoder and decoder handles that correctly.
  • Forgetting revokeObjectURL. If you use createObjectURL for previews, the reference leaks until the page unloads.
  • Assuming Base64 is private. It is trivially reversible. Anyone can paste your string into a Base64 image decoder and see the picture.
  • Inlining everything. The 33% size penalty is real, and it compounds.

Wrapping Up

To convert an image to Base64 in JavaScript, reach for FileReader first. It is the simplest option and the only browser method that preserves your file exactly.

Use canvas when you deliberately want to transform the image, and remember what the round trip costs. Use fetch for remote files, and check the CORS headers before you write a line of code.

Then ask whether you should inline the result at all. We work through that in when Base64 images are worth it and in our guide to Base64 images in CSS and HTML.

Frequently Asked Questions

Why is my Base64 string undefined right after I call readAsDataURL?

+
Because the read is asynchronous. The function returns immediately and the result only exists inside the onload handler. Wrap it in a Promise and await it, or move every line that touches the result inside onload.

I get a SecurityError when calling toDataURL. What is going on?

+
The canvas is tainted. Drawing a cross-origin image onto it locks the canvas so no script can read the pixels back. Set crossOrigin to anonymous before the src, and make sure the remote server sends an Access-Control-Allow-Origin header.

Does FileReader produce a different string than the raw file bytes?

+
No, readAsDataURL encodes the file exactly as it sits on disk. Canvas is the one that differs, because it decodes to pixels and re-compresses. If you need a byte-identical string, never route through canvas.

What is the correct way to strip the data: prefix?

+
Split on the first comma, not on a fixed character count. The prefix length changes with the MIME type, so slicing at a hard-coded number breaks the moment someone uploads a different format.

How do I stop a large file from freezing the page?

+
Read it in a Web Worker. FileReader is available there, and the encoding runs off the main thread, so your UI keeps responding. Anything above about 10 MB is worth moving.

Why does btoa throw on my string but not on the image?

+
btoa only accepts characters in the Latin-1 range, so any emoji or non-Latin text breaks it. Images are binary and safe. For text, encode through TextEncoder first, then Base64 the resulting bytes.

How do I do the same thing in Node.js?

+
Skip all of this and use Buffer. Read the file, then call toString with base64 on the result. There is no FileReader and no canvas in Node, so browser tutorials will send you down the wrong path.

Why is toDataURL with quality 1.0 bigger than my original JPEG?

+
Because you are re-encoding an already-compressed image at maximum quality. The encoder faithfully preserves every compression artefact from the first pass, which costs bits. Use the original file with FileReader instead.

Should I use createObjectURL instead of a Data URL?

+
For previews, yes — it is far faster and uses almost no memory, because it hands back a pointer rather than copying the data. Just call revokeObjectURL when you are finished, or the reference leaks for the life of the document.

Can I convert an image to Base64 without loading it into memory at all?

+
Not in the browser. Every approach reads the whole file before encoding, so a 50 MB image needs roughly 50 MB of memory plus a third for the string. Server-side streaming is the only way around it.

Tags

#JavaScript#Base64#FileReader#Canvas#Data URI