ScreenshotNeo

BlogHow-to

How to Convert HTML and PNG Data to Base64

Convert HTML strings, PNG files, Blobs, and canvas images to Base64 safely in browsers and Node.js, with data URL, Unicode, and troubleshooting examples.

By the ScreenshotNeo team30 September 20269 min read

How to Convert HTML and PNG Data to Base64

To convert a PNG File or Blob in a browser, use FileReader.readAsDataURL(). It returns a complete data URL such as data:image/png;base64,.... Keep the prefix when an HTML <img> or another data-URL consumer needs it; remove the data:*/*;base64, prefix only when an API asks for the raw Base64 payload. For a canvas, use canvas.toDataURL('image/png'). For HTML text, encode UTF-8 bytes before Base64 conversion, especially when the string contains non-ASCII characters. In Node.js, use Buffer.from(value).toString('base64') and Buffer.from(value, 'base64').

What Base64 output actually contains

Base64 is an ASCII representation of bytes. It is not compression and it does not preserve a file name, dimensions, or MIME type by itself. A browser data URL adds that metadata before the payload:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
  • data: identifies a data URL.
  • image/png is the media type.
  • base64 says how the following characters are encoded.
  • The remaining characters are the Base64 payload.

These forms are not interchangeable in every API. An <img src> accepts the complete data URL. A JSON field named image_base64 often expects only the payload. Check the receiving API before stripping or retaining the prefix.

Convert a PNG File or Blob in the browser

FileReader.readAsDataURL() is the normal browser method for a selected file, a fetched Blob, or any other Blob. It is asynchronous, so handle completion with a Promise or the reader events.

A data URL contains a MIME prefix plus the Base64 payload; APIs may require either form.
A data URL contains a MIME prefix plus the Base64 payload; APIs may require either form.
function blobToDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result);
    reader.onerror = () => reject(reader.error);
    reader.readAsDataURL(blob);
  });
}

const input = document.querySelector('input[type=file]');
input.addEventListener('change', async () => {
  const file = input.files[0];
  if (!file) return;

  const dataUrl = await blobToDataUrl(file);
  document.querySelector('#preview').src = dataUrl;

  const rawBase64 = dataUrl.replace(/^data:[^;]+;base64,/, '');
  console.log({ dataUrl, rawBase64 });
});

MDN documents readAsDataURL() for reading a Blob or File. The returned value includes the media-type declaration, so remove that declaration only for a service that explicitly requests raw Base64.

Use a known MIME type when constructing a data URL

If you already have raw Base64 and know the type, construct the complete value yourself:

const dataUrl = `data:image/png;base64,${rawBase64}`;

Do not label JPEG or WebP bytes as PNG. A mismatched MIME type can cause broken previews or incorrect downstream processing.

Convert a canvas image to PNG Base64

For an image already rendered on a canvas, call toDataURL(). Browsers are required to support PNG.

const canvas = document.querySelector('canvas');
const pngDataUrl = canvas.toDataURL('image/png');
const pngBase64 = pngDataUrl.replace(/^data:image\/png;base64,/, '');

console.log(pngDataUrl); // complete data URL
console.log(pngBase64);  // payload only

The canvas documentation notes that the entire image is serialized into an in-memory string. Large canvases can therefore consume substantial memory. Prefer canvas.toBlob() when you can send a Blob directly, and convert that Blob only when a Base64 consumer requires it.

Origin-clean canvases and security errors

If a canvas contains cross-origin pixels without suitable CORS headers, it becomes not origin-clean. Calling toDataURL() can then throw a security error. Set the image element’s crossOrigin attribute before assigning its source and configure the remote server to allow your origin:

const image = new Image();
image.crossOrigin = 'anonymous';
image.onload = () => {
  const canvas = document.createElement('canvas');
  canvas.width = image.naturalWidth;
  canvas.height = image.naturalHeight;
  canvas.getContext('2d').drawImage(image, 0, 0);
  console.log(canvas.toDataURL('image/png'));
};
image.src = 'https://example.com/image.png';

Convert an HTML string to Base64 safely

For ASCII-only HTML, btoa(html) works. btoa() expects a binary string whose character values fit in one byte; it does not accept arbitrary Unicode text. Characters such as emoji, accented letters, and many non-Latin scripts can cause InvalidCharacterError.

function utf8ToBase64(text) {
  const bytes = new TextEncoder().encode(text);
  let binary = '';
  for (const byte of bytes) binary += String.fromCodePoint(byte);
  return btoa(binary);
}

function base64ToUtf8(base64) {
  const binary = atob(base64);
  const bytes = Uint8Array.from(binary, ch => ch.charCodeAt(0));
  return new TextDecoder().decode(bytes);
}

const html = '<main>こんにちは 🌍</main>';
const encoded = utf8ToBase64(html);
const decoded = base64ToUtf8(encoded);
console.log(encoded);
console.log(decoded);

Use btoa() and atob() only with the byte-oriented conversion shown above for Unicode. atob() throws InvalidCharacterError when the input is not valid Base64.

Alternative: encode HTML through a Blob

A Blob gives you a byte-defined workflow and can produce a data URL with a text MIME type:

async function htmlToDataUrl(html) {
  const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
  return blobToDataUrl(blob);
}

const htmlDataUrl = await htmlToDataUrl('<p>Café 🌍</p>');
console.log(htmlDataUrl);

This output is a data:text/html URL. If a service wants only Base64, strip the prefix with a delimiter-aware expression rather than splitting blindly on every comma.

Node.js: encode and decode PNG files and HTML

Node’s Buffer API is the preferred approach for server-side code. The example below reads a PNG, encodes HTML as UTF-8, and decodes the PNG back to bytes.

import { readFile, writeFile } from 'node:fs/promises';

const pngBytes = await readFile('image.png');
const pngBase64 = pngBytes.toString('base64');
const html = '<h1>Café 🌍</h1>';
const htmlBase64 = Buffer.from(html, 'utf8').toString('base64');

const decodedPng = Buffer.from(pngBase64, 'base64');
await writeFile('copy.png', decodedPng);

console.log({ pngBase64, htmlBase64 });

Node documents Buffer.from(str, ‘base64’) and buf.toString('base64') for conversion. Browser-compatibility buffer.atob() and buffer.btoa() functions are legacy choices for new Node API code.

Python and cURL equivalents

Python’s standard library handles bytes directly. Open the PNG in binary mode and encode it; encode HTML after converting the string to UTF-8.

import base64

with open('image.png', 'rb') as image_file:
    png_base64 = base64.b64encode(image_file.read()).decode('ascii')

html = '<h1>Café 🌍</h1>'
html_base64 = base64.b64encode(html.encode('utf-8')).decode('ascii')

print(png_base64)
print(html_base64)

On macOS or Linux, the base64 command can encode a file. GNU and BSD variants differ slightly, so check the local help output for line-wrapping options:

base64 image.png > image.png.b64
base64 --decode image.png.b64 > decoded.png

cURL does not provide a portable general-purpose file-to-Base64 transform. Use your shell’s base64 utility or a language library, then pass the resulting payload to the API that needs it. Avoid putting large Base64 values directly in a command line where shell history can retain them.

Choosing the right representation

Input Best method Output Watch for
File or Blob in a browser FileReader.readAsDataURL() Data URL Strip the prefix only when required
Canvas bitmap canvas.toDataURL('image/png') PNG data URL Memory use and CORS tainting
Unicode HTML TextEncoder + btoa Raw Base64 Never call btoa on arbitrary Unicode
Node file or string Buffer Raw Base64 or bytes Use explicit UTF-8 for strings

Edge cases and limits

  • Base64 increases size. The encoded form is roughly one third larger than the original bytes, before any data-URL prefix.
  • Line breaks matter. Some decoders accept wrapped Base64, while JSON and URL parameters usually expect one continuous string.
  • URL encoding is separate. If Base64 is placed in a query string or form value, URL-encode it. The +, /, and = characters have special meanings in some transports.
  • Validate the type. A payload can be valid Base64 but still contain non-PNG bytes. Check the source MIME type and, when security matters, inspect file signatures server-side.
  • Do not trust decoded HTML. Base64 is not sanitization. Sanitize HTML before inserting it with innerHTML, and treat decoded content as untrusted input.
  • Watch memory. You may hold the original bytes, the Base64 string, and a data URL at the same time. Revoke object URLs and release references after processing large files.

Troubleshooting

InvalidCharacterError from btoa()

The input contains characters outside the byte range, usually Unicode. Encode with TextEncoder first, as shown above.

The image preview is broken

You probably supplied raw Base64 where a data URL was required, used the wrong MIME type, or truncated the payload. Restore data:image/png;base64, and compare the decoded byte length with the source.

toDataURL() throws a security error

The canvas is tainted by cross-origin content. Load images with crossOrigin = 'anonymous' before setting src, and ensure the image server sends compatible CORS headers.

Decoded text contains replacement characters

The original bytes were decoded with the wrong character set. Encode HTML as UTF-8 and decode with TextDecoder() using UTF-8.

Base64 sent through a form field is corrupted

Form encoding can treat + as a space. Send JSON, use a multipart file upload, or URL-encode the value and restore the original characters before decoding.

The browser tab becomes unresponsive

The file or canvas is too large for an in-memory string conversion. Resize before encoding, use toBlob(), process on a worker, or perform conversion on the server.

Performance, reliability, and cost considerations

For small icons, previews, and inline assets, Base64 is convenient. For large images, direct binary uploads are generally more memory-efficient and avoid the encoded size overhead. Cache the encoded value when the same bytes are reused, and do not repeatedly convert a canvas inside an animation loop. In a server pipeline, stream files where possible and impose input-size limits before decoding.

Base64 does not provide confidentiality. Anyone who receives the string can decode it, so use HTTPS and protect tokens and private images. If you store data URLs, apply the same retention and access controls as the original file.

Or skip the browser setup

If your real goal is to obtain an image of a web page before converting it, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all options, including full-page captures, CSS selectors, dark mode, device presets, custom CSS and JavaScript, waits, blocking rules, cookies, headers, geolocation, caching, signed links, asynchronous jobs, bulk capture, and PDF output.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

An MCP server also provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Should I store the data: prefix?

Store it when the value will be used directly as an image or document URL. Remove it only for an API field that explicitly requires raw Base64.

ScreenshotNeo removes common consent banners, popups, and chat widgets before capture.
ScreenshotNeo removes common consent banners, popups, and chat widgets before capture.

Can Base64 convert HTML into an image?

No. It only encodes bytes. To render HTML as an image, use a browser renderer or screenshot service, then encode the resulting image if needed.

Why does the Base64 string end with one or two equals signs?

= is padding added when the input byte length is not a multiple of three. It is normal and should remain in the payload.

Is Base64 smaller than PNG?

No. PNG is a compressed image format; Base64 is an encoding layer and usually makes the byte representation larger.