getgrav/grav · error · RuntimeException

Encoding markdown failed

Error message

Encoding markdown failed

What it means

MarkdownFormatter::encode() assembles the YAML frontmatter and body, then normalizes line endings with preg_replace("/(\r\n|\r)/u", "\n", $encoded). The /u modifier makes PCRE validate the subject as UTF-8; invalid bytes force preg_replace to return null, which this null-check converts into a RuntimeException. Cause: the header array's encoded form or the body string is not valid UTF-8.

Source

Thrown at system/src/Grav/Framework/File/Formatter/MarkdownFormatter.php:103

    public function encode($data): string
    {
        $headerVar = $this->getHeaderField();
        $bodyVar = $this->getBodyField();

        $header = isset($data[$headerVar]) ? (array) $data[$headerVar] : [];
        $body = isset($data[$bodyVar]) ? (string) $data[$bodyVar] : '';

        // Create Markdown file with YAML header.
        $encoded = '';
        if ($header) {
            $encoded = "---\n" . trim($this->getHeaderFormatter()->encode($data['header'])) . "\n---\n\n";
        }
        $encoded .= $body;

        // Normalize line endings to Unix style.
        $encoded = preg_replace("/(\r\n|\r)/u", "\n", $encoded);
        if (null === $encoded) {
            throw new RuntimeException('Encoding markdown failed');
        }

        return $encoded;
    }

    /**
     * {@inheritdoc}
     * @see FileFormatterInterface::decode()
     */
    public function decode($data): array
    {
        $headerVar = $this->getHeaderField();
        $bodyVar = $this->getBodyField();
        $rawVar = $this->getRawField();

        // Define empty content
        $content = [
            $headerVar => [],

View on GitHub (pinned to 6040efed04)

Solutions

  1. Convert the source encoding before saving: mb_convert_encoding($body, 'UTF-8', 'Windows-1252') (identify the real source encoding first).
  2. Drop invalid sequences lossily: mb_convert_encoding($s, 'UTF-8', 'UTF-8') or iconv('UTF-8', 'UTF-8//IGNORE', $s).
  3. Guard inputs with mb_check_encoding($string, 'UTF-8') and reject or sanitize earlier in the pipeline.
  4. Fix the storage layer (utf8mb4 connection charset) so strings arrive clean.

Example fix

// before
$file->save(['header' => $header, 'markdown' => $body]);

// after
$body = mb_convert_encoding((string) $body, 'UTF-8', 'UTF-8');
array_walk_recursive($header, function (&$v) {
    if (is_string($v)) { $v = mb_convert_encoding($v, 'UTF-8', 'UTF-8'); }
});
$file->save(['header' => $header, 'markdown' => $body]);
Defensive patterns

Strategy: validation

Validate before calling

// Validate both parts before encode()
$bodyOk = mb_check_encoding((string) ($data['markdown'] ?? ''), 'UTF-8');
$headerOk = true;
array_walk_recursive($data['header'] ?? [], function ($v) use (&$headerOk) {
    if (is_string($v)) { $headerOk = $headerOk && mb_check_encoding($v, 'UTF-8'); }
});
if (!$bodyOk || !$headerOk) {
    // convert/drop invalid bytes before calling the formatter
}

Type guard

function isUtf8String(mixed $value): bool
{
    return is_string($value) && mb_check_encoding($value, 'UTF-8');
}

Try / catch

try {
    $file->save($data);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'Encoding markdown failed')) {
        $data['markdown'] = mb_convert_encoding($data['markdown'], 'UTF-8', 'UTF-8');
        $file->save($data); // retry once after sanitizing
    }
}

Prevention

When it happens

Trigger: Encoding a markdown file whose frontmatter values or body contain non-UTF-8 bytes: text pasted from Windows-1252/latin1 sources, strings from non-utf8mb4 DB columns, double-encoded or binary-contaminated content flowing into $data['header'] or the body field.

Common situations: Content pasted from word processors in legacy encodings; legacy site imports; DB connections without utf8mb4; metadata (titles, summaries) captured from external feeds with broken encoding.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/ccdd219d1efc1a9c. Report an issue: GitHub.