Multibyte strings7 min readExamples run on PHP 8.5.10

mb_convert_encoding()

Learn how PHP 8.4 mb_convert_encoding() transcodes strings and arrays between encodings, what it returns, how it fails, and which mistakes to avoid.

Signature

mb_convert_encoding(
    array|string $string,
    string $to_encoding,
    array|string|null $from_encoding = null
): array|string|false
Available since PHP 4.0.67.48.08.18.28.38.48.5All versions →

Purpose

mb_convert_encoding() takes text stored in one character encoding and produces the same text in another encoding. A common case is a legacy ISO-8859-1 (Latin-1) value that must become UTF-8 before you store it or send it as JSON. It can also go the other way when an old system demands a single-byte encoding.

A PHP string is only a sequence of bytes. The function does not change what the characters mean. It changes which bytes represent them.

Signature

Parameters and defaults

  • $string: the string to convert, or an array. When you pass an array, string values are converted recursively.
  • $to_encoding: the encoding you want, for example UTF-8 or ISO-8859-1.
  • $from_encoding: the encoding that $string currently uses. The default is null. In that case PHP uses the mbstring.internal_encoding setting if it is set, and otherwise the default_charset setting. You can also pass several candidates, either as an array or as a comma-separated list. PHP then guesses among them with the same algorithm as mb_detect_encoding().

Being explicit about $from_encoding is usually safer than relying on configuration.

Return value and failure cases

On success you get the converted string, or an array with the same shape if you passed an array. On failure the function returns false.

Since PHP 8.0, an invalid encoding name in $to_encoding or $from_encoding throws a ValueError. Before 8.0 it emitted a warning instead. Wrap calls that take encoding names from user input in try/catch.

Example 1: UTF-8 to Latin-1

The accented letter in café takes two bytes in UTF-8 but one byte in Latin-1. The byte lengths show the change.

Example
<?php
$utf8 = "caf\u{00E9}";
$latin1 = mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8');
echo strlen($utf8), "\n";
echo strlen($latin1), "\n";
echo bin2hex($latin1), "\n";
Output
5
4
636166e9

The hex dump ends in e9, the single Latin-1 byte for the accented letter.

Example 2: Latin-1 to UTF-8

Here the input is raw Latin-1 bytes, and the output is valid UTF-8 that json_encode() can handle.

Example
<?php
$latin1 = "\xE9t\xE9";
$utf8 = mb_convert_encoding($latin1, 'UTF-8', 'ISO-8859-1');
echo json_encode($utf8, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo strlen($latin1), ' ', strlen($utf8), "\n";
Output
"été"
3 5

Three bytes become five, because each accented letter now uses two bytes.

Example 3: Arrays and invalid encodings

Arrays are converted recursively, which is convenient for a whole decoded record. An unknown encoding name throws a ValueError.

Example
<?php
$data = ['name' => "\xE9", 'tags' => ["\xE0", 'x']];
$converted = mb_convert_encoding($data, 'UTF-8', 'ISO-8859-1');
echo json_encode($converted, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
try {
    mb_convert_encoding('abc', 'NOPE-9', 'UTF-8');
} catch (ValueError $e) {
    echo get_class($e), "\n";
}
Output
{"name":"é","tags":["à","x"]}
ValueError

Edge cases

  • Characters the target cannot represent. Converting € to ISO-8859-1 cannot keep the character. Bytes that are invalid in the source encoding have the same problem. In both cases mbstring substitutes a replacement character instead of keeping the data. You can inspect or change this with mb_substitute_character(), so check it if you see unexpected question marks.
  • Guessing with several source encodings. A candidate list is a heuristic. Short strings can match more than one encoding, so put the most likely encodings first and prefer a known encoding whenever you have one.
  • Same encoding in and out. Converting UTF-8 to UTF-8 is not a reliable way to repair broken input, so validate with mb_check_encoding() when you need a yes-or-no answer.
  • Changelog. Since 7.2.0 arrays are accepted. Since 8.0.0 $from_encoding is nullable and invalid encodings throw ValueError. Since 8.2.0 the function no longer returns the non-text encodings Base64, QPrint, UUencode, HTML entities, 7 bit and 8 bit.

Common mistakes

  1. Converting text that is already in the target encoding. Treating UTF-8 bytes as Latin-1 and converting them produces mojibake such as an accent turned into two odd characters. Find out the true source encoding first.
  2. Omitting $from_encoding and trusting the defaults. Behavior then depends on mbstring.internal_encoding and default_charset, which can differ between servers.
  3. Expecting false for bad names. In PHP 8 an invalid encoding name throws instead.
  4. Using it for escaping. Changing the encoding does not make output safe for HTML, SQL or shell use.
  5. Measuring with strlen() and expecting character counts. strlen() counts bytes. Use mb_strlen() with an explicit encoding for characters.
  • mb_detect_encoding() and mb_detect_order() for guessing and ordering candidate encodings.
  • mb_check_encoding() to validate a string.
  • mb_substitute_character() to control replacement behavior.
  • iconv() and UConverter::transcode() as alternative converters.

Recap

Use mb_convert_encoding() to change the byte representation of text, name the source encoding explicitly, and expect a ValueError for invalid encoding names. Arrays convert recursively, and characters the target encoding cannot hold are substituted rather than kept.

Tested examples

Convert UTF-8 to ISO-8859-1 and inspect the bytes
<?php
$utf8 = "caf\u{00E9}";
$latin1 = mb_convert_encoding($utf8, 'ISO-8859-1', 'UTF-8');
echo strlen($utf8), "\n";
echo strlen($latin1), "\n";
echo bin2hex($latin1), "\n";
Output
5
4
636166e9
Convert Latin-1 bytes to UTF-8
<?php
$latin1 = "\xE9t\xE9";
$utf8 = mb_convert_encoding($latin1, 'UTF-8', 'ISO-8859-1');
echo json_encode($utf8, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
echo strlen($latin1), ' ', strlen($utf8), "\n";
Output
"été"
3 5
Convert an array recursively and catch an invalid encoding
<?php
$data = ['name' => "\xE9", 'tags' => ["\xE0", 'x']];
$converted = mb_convert_encoding($data, 'UTF-8', 'ISO-8859-1');
echo json_encode($converted, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
try {
    mb_convert_encoding('abc', 'NOPE-9', 'UTF-8');
} catch (ValueError $e) {
    echo get_class($e), "\n";
}
Output
{"name":"é","tags":["à","x"]}
ValueError