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.

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/pngis the media type.base64says 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.

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.

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.


