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

  1. Convert the data to UTF-8 before rendering: $out = mb_convert_encoding($in, 'UTF-8', 'Windows-1252'); (or iconv)
  2. Fix the source encoding at the connection layer (e.g. PDO mysql DSN charset=utf8mb4) or file reading (stream filter convert.iconv)
  3. Check validity first with mb_check_encoding($s, 'UTF-8') and sanitize/reject invalid input
  4. 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

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


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)