mb_convert_encoding

Converts a string or array of strings from one character encoding to another.

Signature
mb_convert_encoding(array|string $string, string $to_encoding, array|string|null $from_encoding = null): array|string|false
  • Added in PHP 4.0.6

Parameters

ParameterTypeDescription
$stringarray|stringThe string, or array of strings, to convert. Arrays are converted value by value.
$to_encodingstringThe encoding the result should be in.
$from_encodingoptionalarray|string|null= nullThe encoding the input is currently in. Several candidates may be given as an array or a comma-separated list, in which case mbstring picks one by detection. If omitted or null, the internal encoding is used.

Return value

array|string|falseThe converted string, or an array with every value converted, or false on failure.

Errors

Throws a ValueError as of PHP 8.0 when either encoding name is not recognised by mbstring.

Changelog

  • PHP 8.2.0The non-text encodings "Base64", "QPrint", "UUencode", "HTML entities", "7 bit" and "8 bit" are no longer produced by this function.
  • PHP 8.0.0A ValueError is thrown when to_encoding or from_encoding is an invalid encoding name; previously a warning was emitted.
  • PHP 8.0.0from_encoding became nullable.
  • PHP 7.2.0string may now be an array; previously only strings were accepted.

mb_convert_encoding() re-encodes text: same characters, different bytes. It is the function you use at an application boundary, where data arrives in one encoding and the rest of your system speaks another — typically a legacy Windows-1252 CSV, an EUC-JP feed, or an old MySQL table, all being normalised to UTF-8.

Always pass from_encoding explicitly when you know it. Leaving it out makes the function assume the internal encoding, which is almost never what a file from elsewhere is in, and the resulting mojibake is hard to trace back. Passing a list of candidate encodings makes mbstring guess, with all the unreliability that implies; a single named encoding is far safer.

Conversion is lossy in one direction. Going from UTF-8 to a smaller encoding, any character with no equivalent is replaced by the substitute character set with mb_substitute_character() — ? by default. Those characters are gone; converting back does not bring them home. Converting the other way, from a single-byte encoding to UTF-8, is lossless.

Double conversion is the classic bug. Running an already-UTF-8 string through a Latin-1-to-UTF-8 conversion turns é into é. Guard the call with mb_check_encoding() so you only convert what actually needs converting.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Normalise a Windows-1252 CSV export to UTF-8 before importing it.
  • Produce a Shift_JIS file for a legacy Japanese partner system that cannot read UTF-8.
  • Convert every value of a $_POST array to UTF-8 in one call.
  • Re-encode rows read from an old latin1 database column so they render correctly on a UTF-8 page.

Frequently asked questions

How do I convert ISO-8859-1 to UTF-8 in PHP?

Call mb_convert_encoding($string, 'UTF-8', 'ISO-8859-1'). Always name the source encoding rather than letting it default, or the function will assume the internal encoding and produce mojibake.

What is the difference between mb_convert_encoding and iconv?

Both transcode text, but mb_convert_encoding() replaces unmappable characters with a substitute character by default and can convert an entire array, while iconv() fails or truncates unless you append //TRANSLIT or //IGNORE to the target encoding.

Why does my text turn into é after conversion?

Because the string was already UTF-8 and you converted it as if it were Latin-1, so each UTF-8 byte was treated as a separate character. Guard the call with mb_check_encoding($string, 'UTF-8') and skip conversion when it returns true.

More examples

Convert a whole array in one call

Since PHP 7.2 the first argument may be an array, and every value is converted while the keys are left alone. The hex dump shows München stored as single bytes (fc for ü) and the euro sign as the single byte 80, which is what Windows-1252 uses. The round trip back to UTF-8 restores the originals.

Example · php
<?php
$row = ['city' => 'München', 'note' => 'Preis 10€'];

$w1252 = mb_convert_encoding($row, 'Windows-1252', 'UTF-8');

print_r(array_map('bin2hex', $w1252));
print_r(mb_convert_encoding($w1252, 'UTF-8', 'Windows-1252'));

What happens to characters the target encoding cannot hold

Latin-1 has no Japanese characters, so each one is either replaced by ? or dropped entirely, depending on the substitute character setting. The é survives because Latin-1 does have it. Both results are lossy: converting back to UTF-8 gives you the question marks, not the kanji.

Example · php
<?php
$src = '日本 café';

mb_substitute_character(0x3F);          // '?'
$a = mb_convert_encoding($src, 'ISO-8859-1', 'UTF-8');
echo mb_convert_encoding($a, 'UTF-8', 'ISO-8859-1'), "\n";

mb_substitute_character('none');        // drop them
$b = mb_convert_encoding($src, 'ISO-8859-1', 'UTF-8');
echo mb_convert_encoding($b, 'UTF-8', 'ISO-8859-1'), "\n";

Check before you convert

The guarded loop prints "café" twice, because it converts only the row that needs it. The last line shows what happens without the guard: the already-correct UTF-8 string is reinterpreted byte by byte and comes out as "café". Mixed-encoding imports need the check, not a blanket conversion.

Example · php
<?php
$rows = ["café", "caf\xE9"];   // already UTF-8, then Latin-1

foreach ($rows as $raw) {
    $utf8 = mb_check_encoding($raw, 'UTF-8')
        ? $raw
        : mb_convert_encoding($raw, 'UTF-8', 'Windows-1252');
    echo $utf8, "\n";
}

// converting blindly mangles the first row
echo mb_convert_encoding($rows[0], 'UTF-8', 'Windows-1252'), "\n";

Discussion

  • Be the first to comment on this lesson.