Encode and decode a string
PHP strings are byte strings, so base64_encode encodes whatever bytes the string holds. A source file saved as UTF-8 gives you UTF-8 Base64 with no conversion step:
<?php
$enc = base64_encode("héllo wörld");
echo $enc; // aMOpbGxvIHfDtnJsZA==
echo base64_decode($enc); // héllo wörld
If text comes from a database or a form in another charset, convert it first with mb_convert_encoding($s, 'UTF-8', 'Windows-1252'). Otherwise the Base64 is correct for those bytes but decodes to mojibake elsewhere. The Base64 to text tool lets you try different charsets on the result.
Strict mode: base64_decode($s, true)
Without the second argument, base64_decode throws away any character that is not in the alphabet and decodes the rest. With true, it returns false instead:
var_dump(base64_decode("aGk=!!")); // string(2) "hi"
var_dump(base64_decode("aGk=!!", true)); // bool(false)
Always check with ===, because a valid empty input decodes to "", which is falsy:
$bytes = base64_decode($input, true);
if ($bytes === false) {
throw new InvalidArgumentException('Invalid Base64');
}
Strict mode is less strict than the name suggests. These were tested on PHP 8.4:
| Input | Strict result |
|---|---|
aGk (no padding) | "hi" |
"aGk=\n" (trailing newline) | "hi" |
aGl= (non-zero padding bits) | "hi" |
aGk== (excess padding) | false |
a (impossible length) | false |
PDw_Pz8-Pg== (URL-safe chars) | false |
If you need canonical Base64 only, strip whitespace, decode in strict mode, then check base64_encode($bytes) === $input. The Base64 validator runs the same checks and points at the bad character.
URL-safe Base64 with and without padding
PHP has no built-in Base64URL function. Swap the two characters with strtr, which is faster than str_replace for single-character maps:
function b64url_encode(string $data, bool $pad = false): string {
$s = strtr(base64_encode($data), '+/', '-_');
return $pad ? $s : rtrim($s, '=');
}
function b64url_decode(string $s): string|false {
return base64_decode(strtr($s, '-_', '+/'), true);
}
echo b64url_encode("<<???>>"); // PDw_Pz8-Pg
echo b64url_encode("<<???>>", true); // PDw_Pz8-Pg==
echo b64url_decode("PDw_Pz8-Pg"); // <<???>>
Because strict mode tolerates missing padding, there is no need to add = back before decoding. More on the differences in Base64 vs Base64URL.
Encode and decode a file
echo base64_encode(file_get_contents("hello.txt")); // aGVsbG8K
file_put_contents("copy.txt", base64_decode("aGVsbG8K", true));
For a data URI, prepend the MIME type: 'data:image/png;base64,' . base64_encode($png). mime_content_type($path) can detect the type if fileinfo is enabled.
Large files with stream filters
file_get_contents loads the whole file and the Base64 copy, so memory use is about 2.3 times the file size. The convert.base64-encode stream filter encodes as it copies:
$in = fopen("big.bin", "rb");
$out = fopen("big.b64", "wb");
stream_filter_append($out, "convert.base64-encode");
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);
Decoding works the same way, or in one line through a php://filter URL:
copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");
Both produced output identical to the in-memory functions on a 300 KB test file. A manual loop also works if every chunk is a multiple of 3 bytes, for example fread($in, 3 * 8192). Any other size inserts = in the middle of the output.
MIME line wrapping
Email bodies need lines of at most 76 characters. chunk_split does exactly that, with \r\n line endings by default:
echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4
It also adds a trailing \r\n. Mail libraries do this for you; the MIME Base64 encoder shows the format.
Common pitfalls
- Forgetting strict mode on untrusted input. Bad data decodes to random bytes and fails somewhere far away from the cause.
- URL-safe input in default mode.
base64_decode("PDw_Pz8-Pg==")returnsstring(6) "<<���", not<<???>>. Translate withstrtrfirst. - Passing
null. In PHP 8.1+ you getDeprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated. Passing an array throwsTypeError: base64_encode(): Argument #1 ($string) must be of type string, array given. +turning into a space. Standard Base64 in a query string gets mangled by URL decoding, because+means space there. Use the URL-safe variant orrawurlencode.- Treating it as a password store. Base64 hides nothing. Use
password_hashfor passwords and see encoding vs encryption.
Version notes
base64_encode,base64_decodeand the$strictflag exist in all supported PHP versions (strict mode was added in 5.2).- Since PHP 8.0,
base64_decodeis declared as returningstring|false, and passing an array or object throwsTypeErrorinstead of a warning. - The
convert.base64-encodefilter acceptsline-lengthandline-break-charsoptions for MIME output, for examplestream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).
FAQ
What does the second parameter of base64_decode do?
base64_decode($s, true) enables strict mode: the function returns false when the input contains characters outside the Base64 alphabet. Without it, those characters are silently dropped.
Does PHP have a base64url_encode function?
No. Use rtrim(strtr(base64_encode($data), '+/', '-_'), '=') to encode and base64_decode(strtr($s, '-_', '+/'), true) to decode. Strict mode accepts the missing padding.
Is base64_decode strict mode a full validator?
No. It still accepts whitespace, missing padding and non-zero padding bits such as aGl=. For exact validation, re-encode the result and compare it with the normalized input.
Why does base64_decode return garbage instead of false?
You probably called it without true. In the default mode, base64_decode('PDw_Pz8-Pg==') drops the _ and - and returns six wrong bytes. Translate URL-safe input with strtr first.