twigphp/Twig · error · RuntimeError
The string to escape is not a valid UTF-8 string.
Error message
The string to escape is not a valid UTF-8 string.
What it means
EscaperRuntime::escape (js escaping path) requires the input string to be valid UTF-8; it converts from the configured charset to UTF-8 if needed and then runs preg_match('//u') as a validity check. On failure it throws this RuntimeError rather than emitting malformed escaped output.
Solutions
- Convert the data to UTF-8 before rendering: $out = mb_convert_encoding($in, 'UTF-8', 'Windows-1252'); (or iconv)
- Fix the source encoding at the connection layer (e.g. PDO mysql DSN charset=utf8mb4) or file reading (stream filter convert.iconv)
- Check validity first with mb_check_encoding($s, 'UTF-8') and sanitize/reject invalid input
- Ensure Twig's charset option matches your actual output encoding
Example fix
// before
{{ legacyLatin1String|escape('js') }}
// after
{{ (legacyLatin1String|convert_encoding('UTF-8', 'ISO-8859-1'))|escape('js') }} Defensive patterns
Strategy: validation
Validate before calling
if (!mb_check_encoding($value, 'UTF-8')) { $value = mb_convert_encoding($value, 'UTF-8', 'Windows-1252'); } Type guard
function isUtf8(string $s): bool { return mb_check_encoding($s, 'UTF-8'); } Try / catch
try { $html = $twig->render($name, $ctx); } catch (\Twig\Error\RuntimeError $e) { if (str_contains($e->getMessage(), 'valid UTF-8')) { /* sanitize/convert input and retry */ } } Prevention
- Validate/normalize all external data to UTF-8 before rendering
- Set correct DB connection charset (utf8mb4)
- Use mb_substr/mb_* string functions on multibyte data
- Match Twig's charset option to actual output encoding
When it happens
Trigger: Passing a string with invalid UTF-8 bytes to the 'js' escaper (e | escape('js')) while charset is UTF-8, or a string whose declared source charset conversion still yields invalid UTF-8; commonly data read from files/DBs in another encoding or binary payloads.
Common situations: Legacy databases or APIs returning ISO-8859-1/Windows-1252 data not converted before rendering; user-uploaded files with mixed encodings; truncated multi-byte characters from substr() instead of mb_substr; binary blobs interpolated into templates.
Related errors
- Failed to load Twig template
- The "cycle" function expects a non-empty sequence.
- The "random" function cannot pick from an empty sequence or…
- Regexp " " passed to "matches" failed: .
- Trimming side must be "left", "right" or "both".
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/5cf1b8ee87398e3c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Runtime/EscaperRuntime.php:185
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset);
}
$string = $this->convertEncoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8');
return iconv('UTF-8', $charset, $string);
case 'js':
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if ('UTF-8' !== $charset) {
$string = $this->convertEncoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', static function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
$short = match ($char) {
'\\' => '\\\\',
'/' => '\\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',View on GitHub (pinned to a414c3a491)