twig/markdown-extra
Twig extension adding Markdown support to templates. Provides markdown_to_html and html_to_markdown filters to convert Markdown blocks to HTML and turn HTML back into Markdown, making it easy to render or edit rich content in Twig views.
Install the package (updated for security patch):
composer require twig/markdown-extra:^3.26
Register the Twig extension in AppServiceProvider:
use Twig\Extra\Markdown\MarkdownExtension;
public function boot()
{
$this->twig->addExtension(new MarkdownExtension());
}
For Laravel 10+, add to config/twig.php under 'extensions'.
First use case (now with auto-escaping for security):
<div class="content">
{{ $article->body|markdown_to_html }} <!-- Note: {{ }} instead of {!! !!} for auto-escaping -->
</div>
markdown_to_html: Converts Markdown to auto-escaped HTML (CVE-2026-46637 fix).html_to_markdown: Converts HTML back to Markdown (unchanged).{{ }} (auto-escaping) for user-generated content. The package now pre-escapes input on HTML-emitting filters.{{ }} (auto-escaping) by default. Only use {!! !!} with explicit sanitization.<article>
{{ $comment->text|markdown_to_html }}
</article>
<article>
{!! Str::of($comment->text|markdown_to_html)->allowedTags(['p', 'strong', 'em', 'a']) !!}
</article>
Cache::remember("comment_{$comment->id}_html", now()->addHours(1), function() use ($comment) {
return e($comment->text|markdown_to_html); // Explicit escaping
});
$markdown = file_get_contents(storage_path("app/docs/api.md"));
return view('docs.api', ['content' => $markdown]);
<div class="docs">
{{ $content|markdown_to_html }} <!-- Auto-escaped -->
</div>
$legacyHtml = '<p>Old <strong>HTML</strong> content</p>';
$markdown = $legacyHtml|html_to_markdown;
file_put_contents("storage/app/migrated/{$id}.md", $markdown);
// In AppServiceProvider
$this->twig->addFilter(new class extends \Twig\TwigFilter {
public function __invoke($markdown, $customOption = null) {
$html = (new \Symfony\Component\Markdown\Markdown())->convert($markdown);
return $customOption ? e($html) . '<!-- custom: '.$customOption.' -->' : e($html);
}
}, new \Twig\TwigFilter('markdown_custom', [$this, 'markdownCustomFilter']));
{{ $content|markdown_custom('highlight') }} <!-- Auto-escaped -->
$html = Cache::remember("markdown_{$id}", now()->addDays(7), function() use ($markdown) {
return e($markdown|markdown_to_html);
});
$markdown = request()->input('content')|html_to_markdown;
$post->update(['body' => $markdown]);
{{ }}):
<div x-data="{ preview: '' }" x-init="
$watch('input', (val) => {
preview = val|markdown_to_html;
})
">
<textarea x-model="input"></textarea>
<div x-html="preview"></div> <!-- Use x-html for raw output -->
</div>
XSS Risks (Critical Fix in v3.26.0):
{!! !!} now auto-escapes by default. Malicious payloads like <script>alert(1)</script> are blocked.{{ }} for all user-generated content.{!! Str::of($userInput|markdown_to_html)->allowedTags(['p', 'a']) !!}
Purifier for strict sanitization:
use Illuminate\Support\Str;
$safeHtml = Str::of($markdown|markdown_to_html)->purify();
Double Escaping (New Behavior):
{{ $var|markdown_to_html }} twice may break rendering (input is escaped twice).$cachedHtml = Cache::remember("markdown_{$id}", now()->addDays(1), function() use ($var) {
return $var|markdown_to_html;
});
{{ $cachedHtml }}
Performance with Large Content (Unchanged):
$html = Cache::remember("markdown_{$id}", now()->addDays(7), function() use ($markdown) {
return $markdown|markdown_to_html;
});
HTML-to-Markdown Limitations (Unchanged):
\Log::debug('Markdown Input:', [$markdown]);
\Log::debug('HTML Output (escaped):', [e($markdown|markdown_to_html)]);
<, >, &).<img src=x onerror=alert(1)> should be blocked).$markdown = new \Symfony\Component\Markdown\Markdown();
$markdown->setEnvironment('dev');
Twig Auto-Reloading (Unchanged):
php artisan view:clear
Extension Registration (Unchanged):
config/twig.php includes:
'extensions' => [
Twig\Extra\Markdown\MarkdownExtension::class,
],
Markdown Extra Features (Unchanged):
$markdown = new \Symfony\Component\Markdown\Markdown();
$markdown->addExtension(new \Symfony\Component\Markdown\Extension\TableExtension());
$this->twig->addFilter(new class extends \Twig\TwigFilter {
How can I help you explore Laravel packages today?