The quickest way to Base64-encode a file from the command line is the built-in base64 tool, which ships with both macOS and every Linux distribution. Encode a file to a text blob, decode it back, or one-line it straight into a data URI:
# Encode a file to Base64 (writes the text to out.txt):
base64 in.png > out.txt
# Decode it back to the original binary (GNU/Linux):
base64 -d out.txt > in.png
# Decode it back to the original binary (macOS):
base64 -D out.txt > in.pngThat -d vs -D split is the single thing that trips people up, so I am putting it up front. Everything else below is the detail: the line-wrapping difference between the two base64 implementations, building a complete data: URI from an image, encoding a short string instead of a file, and why none of this is encryption.
Base64 is encoding, not encryption. It scrambles nothing. Anyone with the blob can run
base64 -dand get your file back. It exists to carry binary data safely through text-only channels (email, JSON, a CSS file, an env var), not to hide it. If you need secrecy, encrypt first (gpg,age,openssl) and Base64 the ciphertext if you also need it text-safe.
The macOS vs GNU decode flag (the cross-platform gotcha)
There are two base64 programs in the wild and they disagree on how you ask for a decode:
| Task | GNU coreutils (Linux) | BSD (macOS) |
|---|---|---|
| Encode a file | base64 in.png | base64 in.png or base64 -i in.png |
| Decode | base64 -d (or --decode) | base64 -D (or --decode) |
| One unwrapped line | base64 -w 0 in.png | wrapping is off by default |
Encoding is identical on both, so a plain base64 in.png works everywhere. Decoding is where a copied command breaks. On Linux the flag is a lowercase -d; on macOS the BSD tool wants an uppercase -D. The nasty part is what lowercase -d does on macOS: it is not an error, it is the --debug flag (verbose log messages), so base64 -d out.txt on a Mac silently encodes the file with extra logging instead of decoding it. A copied-from-Linux decode command on macOS therefore fails quietly, which is worse than a hard error. The good news: --decode (the long form) works on both, so if you want one command that runs unchanged on a Mac and a Linux box, write it with the long flag:
# Portable decode, works on macOS and GNU/Linux:
base64 --decode out.txt > in.pngmacOS also gives you explicit input and output flags, which read nicely in a script and avoid shell redirection:
# macOS only: -i input file, -o output file
base64 -i in.png -o out.txt # encode
base64 -D -i out.txt -o in.png # decodeThose -i/-o flags are a BSD thing and are not present on GNU base64, so keep them out of anything you need to run on Linux.
The line-wrapping difference
Run the same encode on a Mac and on a Linux box and you can get visibly different output. GNU base64 wraps the encoded text at 76 columns by default, inserting a newline every 76 characters. BSD base64 on macOS does not wrap at all by default, so you get one long line.
The wrapping matters when you feed the Base64 somewhere that hates newlines, such as a data URI, an HTTP header, a Kubernetes secret, or an env var. To force a single unbroken line:
# GNU/Linux: disable wrapping with -w 0
base64 -w 0 in.png
# macOS BSD: already one line by default; if you want to be explicit, -b 0
base64 -b 0 -i in.pngNote the flag letters differ again: GNU uses -w (wrap), BSD uses -b (break). Going the other way, if you have a wrapped blob and need it on one line, strip the newlines with tr:
base64 in.png | tr -d '\n'That tr -d '\n' trick is the portable equalizer. It works regardless of which base64 produced the input, so I reach for it in scripts that have to run on both platforms.
Build a data URI from an image
A data URI inlines an image directly into HTML or CSS as data:<mime>;base64,<data>, which saves a request for small assets. The whole thing is one line once you have the unwrapped Base64:
# GNU/Linux:
echo "data:image/png;base64,$(base64 -w 0 in.png)"
# macOS (already unwrapped, so no -w needed):
echo "data:image/png;base64,$(base64 -i in.png)"Mind the MIME type: image/png for PNG, image/jpeg for JPEG, image/svg+xml for SVG, image/webp for WebP. The MIME prefix is not optional. A browser will not sniff it from the bytes inside a data URI, so data:image/png;... on a JPEG renders nothing.
Keep data URIs for genuinely small images. Base64 inflates the byte count by roughly 33 percent, and inlining a large image bloats the HTML or CSS that carries it (and that file usually is not cached as aggressively as a standalone image would be). For anything beyond an icon or a tiny background, ship the real file. If you are converting images on the command line anyway, convert an image to WebP from the command line first, then inline the smaller result.
Encode or decode a short string
For a quick string rather than a file, pipe echo into base64. There is one caveat that bites everyone at least once: echo appends a trailing newline, and base64 encodes that newline as part of the data. So echo "hello" | base64 is encoding hello\n, not hello. Use echo -n (or printf) to suppress it:
# Wrong: encodes "hello\n" (the trailing newline is included)
echo "hello" | base64 # aGVsbG8K
# Right: encodes exactly "hello"
echo -n "hello" | base64 # aGVsbG8=
printf '%s' "hello" | base64 # aGVsbG8= (printf adds no newline at all)I prefer printf '%s' because echo -n is not portable to every shell, whereas printf behaves the same everywhere. Decoding a string is the same --decode story as files:
echo "aGVsbG8=" | base64 --decode # hello (no trailing newline)If you are encoding a string only to move it onto your clipboard, pipe it straight through: on macOS, printf '%s' "hello" | base64 | pbcopy puts the encoded text on the clipboard ready to paste. See copy to the clipboard from the command line with pbcopy and pbpaste for that workflow.
Verify a round-trip
Because Base64 is lossless, a decode of an encode should give you back the exact original bytes. After moving a binary through a text channel, confirm nothing got mangled by comparing checksums of the original and the decoded copy:
base64 in.png > out.txt
base64 --decode out.txt > roundtrip.png
# Compare hashes; identical means a clean round-trip
shasum -a 256 in.png roundtrip.pngIf the two hashes differ, something stripped or rewrote a character in transit (a common cause is a tool that wrapped or unwrapped the lines, or mangled the trailing = padding). See verify a file checksum on macOS for the full hashing flow.
FAQ
See also
- Copy to the clipboard from the command line with pbcopy and pbpaste: pipe Base64 straight onto the clipboard, plus the Linux and Windows equivalents.
- Verify a file checksum on macOS: confirm a Base64 round-trip is byte-for-byte identical with shasum.
- Convert an image to WebP from the command line: shrink an image before you inline it as a data URI.
Sources
Authoritative references this article was fact-checked against.





