Encode an image as Base64
Encode a picture as Base64 and read back what it cost: the character count, the overhead to one decimal place, and whether the file is small enough that inlining it is worth doing.
How it works
Base64 is a transport rather than a format. It carries arbitrary bytes through a channel that tolerates only text, and it does that by spending six bits per character. Sixty-four printable characters cover every six-bit value, so twenty-four bits of input divide evenly into four characters of output: three bytes in, four characters out. RFC 4648 section 4 fixes the alphabet as the twenty-six uppercase letters, the twenty-six lowercase, the ten digits, a plus sign and a forward slash. Nothing about the picture itself is read, altered or compressed on the way through.
The final group is where the rule earns attention. An input length that is not a multiple of three leaves one or two bytes over. One leftover byte becomes two characters followed by two equals signs; two leftover bytes become three characters followed by one. Padding is not data. It restores the four-character group so a decoder stepping in fixed units can tell where the string ended, and RFC 4648 section 3.2 requires it wherever the length is not already known from somewhere else.
The 33.3% floor is arithmetic
Four characters for every three bytes is a ratio of exactly four thirds, so an encoded string runs 33.33% longer than the file it carries before anything further is added. Padding lifts that fraction higher on any file whose length is not divisible by three, and the effect is dramatic on tiny inputs: a single byte encodes to four characters, an overhead of 300%. Line wrapping adds one character per line. A data URI adds its prefix, and `data:image/png;base64,` is twenty-two characters on its own. The figure reported beside the result is measured from the output, so the padding shows up instead of being rounded away.
RFC 2397 and the ;base64 token
The data URI is defined by RFC 2397, published in August 1998. Its grammar is short: the scheme, an optional media type, an optional `;base64` token, a comma, then the data. Omit the token and the data after the comma is read as percent-encoded text instead. Omit the media type entirely and the specification says the content is `text/plain;charset=US-ASCII`, so the type is worth keeping even in places where a browser would recover.
That grammar contains no whitespace production. A line break inside the encoded run is outside the specification, and the wrapping control here therefore applies to the bare string alone. Wrapping exists because MIME bodies are line-oriented: RFC 2045 section 6.8 caps a Base64 line at 76 characters, and the same section directs a decoder to ignore any character outside the alphabet, so whether the break is a line feed or a carriage-return pair cannot change the bytes that come back out.
Plus and slash are safe here and unsafe in a query string
The two punctuation characters in the alphabet are the whole reason a second alphabet exists. Inside a data URI both are ordinary: RFC 3986 lists the plus sign among the sub-delimiters, and a slash is legal anywhere after the scheme. Move the same string into a query parameter and the plus is in danger, because form encoding writes a space as a plus sign and any receiver applying those rules converts every plus in the payload into a space. The string still decodes afterwards, into different bytes, with no error raised anywhere. RFC 4648 section 5 answers that with base64url: hyphen for plus, underscore for slash.
Two costs that inlining does not remove
An inlined image cannot be cached on its own. It has become part of the document, so it inherits that document's cache lifetime. A content-hashed image file can be served with max-age=31536000 and the immutable extension of RFC 8246, which suppresses revalidation for a year; the HTML or the stylesheet holding an inlined copy cannot be, because its own content keeps changing. Inlining converts an asset fetched once into bytes retransmitted whenever the wrapper is.
The second cost is timing. A stylesheet blocks rendering until it has finished arriving, so an encoded string inside one delays first paint by the whole of its own download. RFC 6928 raised TCP's initial congestion window to ten segments, near enough 14,600 bytes delivered before the sender has to wait for an acknowledgement. Anything inlined spends part of that window, and it spends it ahead of the markup and the styles that decide what the first paint contains.
The 33% is smaller over the wire than it looks, because gzip recovers most of it on bytes that were already compressed. Measured this session: a 268,598-byte PNG gzips to 234,283, and the Base64 of that same file gzips to 246,605, an excess of 5.3%. Transfer size is therefore not the reason to hold back. The threshold applied on this page is about 3 KB of source, a fifth of the initial window, and past 8 KB the request that inlining saves costs less than the delay it adds.
A vector file wants percent-encoding instead
An SVG is text already. Writing `data:image/svg+xml,` followed by the markup works directly, and only the characters RFC 3986 excludes from a URI have to be escaped on the way: the percent sign, the hash that would otherwise open a fragment, the angle brackets, the braces, the double quote and the spaces. The apostrophe and the ampersand are sub-delimiters and stay as they are, which matters, because attribute-heavy markup is full of both.
The character count alone does not settle it. Measured here on a 287-byte icon: the percent-encoded URI is 432 characters and the Base64 one 410, so the encoded form looks shorter. Gzip the two and the order reverses, 244 bytes against 321, with brotli further apart at 209 against 305. Base64 spreads every repeated attribute across a shifting six-bit boundary and the compressor stops recognising it. Dropping a vector file here shows both forms with both figures, measured on that file rather than on this one.
Questions
Why is the string a third larger than my file?
Because six bits of every byte fit into one character of the alphabet, so three bytes always take four characters. The ratio is fixed and nothing on this page changes it. Over the wire the gap narrows a long way: gzip recovers most of the expansion on bytes that were already compressed, and measured here the Base64 of a 268,598-byte PNG gzips to 246,605 against 234,283 for the file itself, an excess of 5.3%. In memory and in your source file it is still a third longer.
Does the media type come from the filename?
No. The first bytes of the file are matched against the known signatures and the result is shown with the bytes that produced it. A PNG saved as photo.jpg is reported as image/png, which is the correct answer and usually a surprise. Where the bytes match no known image signature the type falls back to application/octet-stream and the row says so.
Can a data URI go in a Markdown file?
CommonMark places no restriction on the destination inside an image's parentheses, and a Base64 string contains no spaces, brackets or parentheses, so the syntax holds and the file is valid Markdown. Whether it appears is decided further down the pipeline: a renderer that sanitises its HTML output works from a scheme allow-list, and a data URI survives only if that list includes the scheme. Test one small image in the renderer you are targeting before encoding a set.
Why does the line-wrap setting do nothing to the data URI?
RFC 2397's grammar has no place for whitespace, so a newline inside the encoded run makes the URI invalid even where a lenient browser accepts it. Wrapping belongs to MIME bodies, which are line-oriented by design. The setting therefore applies to the bare Base64 form and is reported as ignored on the other four.
Is the image uploaded anywhere?
No. The encoding runs in a Web Worker in this tab. Open the Network tab of your browser's developer tools and encode a file: the page's own scripts load once and nothing else is requested. There is no server side to this tool, so there is nothing for an image to be sent to.