kwattro/markdown-bundle
A Symfony bundle that integrates Markdown parsing into your application, offering a configurable parser service and Twig helpers for rendering Markdown content in templates. Useful for lightweight docs, CMS content, and user-authored text.
Know what you're actually installing first. This bundle is a thin Symfony wrapper around the Sundown markdown parser — which means the real dependency is the native sundown PHP extension (PECL), not just a Composer package. Check that before anything else:
php -m | grep -i sundown
composer require kwattro/markdown-bundle
If sundown isn't in php -m, Composer will install fine but the parser will fail at runtime — see Gotchas.
Register the bundle. This is a Symfony 2/3-era bundle, so it goes in app/AppKernel.php:
new Kwattro\MarkdownBundle\KwattroMarkdownBundle(),
(Symfony 4+ projects will need config/bundles.php instead, and may hit compatibility issues.)
Find the service before you guess its ID. Documentation is sparse, so let the container tell you:
bin/console debug:container | grep -i markdown
You're looking for a parser service (conventionally something like kwattro_markdown or ...markdown.parser) exposing a render()/transform()-style method.
First use case — the 90% case. Render markdown stored in an entity field into HTML in a template:
{{ post.body|markdown }}
Most usage of this bundle is exactly that one line, plus the safety caveat below.
Twig filter for read-time rendering. The bundle registers a Twig extension so you can render inline. Note the escape interaction — the filter typically returns HTML that Twig may still escape; either the extension is marked is_safe: html or you need |raw:
{{ post.body|markdown|raw }}
Test this once and standardise, because getting it wrong produces either escaped tags or an XSS hole.
Inject the service for anything non-trivial. Don't scatter render logic through templates. Wrap it in your own service:
class MarkdownRenderer
{
public function __construct(private $parser) {}
public function toHtml(string $md): string
{
return $this->parser->render($md);
}
}
The indirection matters here: this package is effectively unmaintained (5 stars, 0 dependents), so isolating the call behind one small class means swapping in league/commonmark later is a one-file change.
Store both source and output. For content-heavy apps, render on save into a body_html column and cache it. This keeps the C parser out of hot read paths and decouples display from the parser being present on every node.
Enable Sundown extensions via config. Sundown supports tables, fenced code blocks, autolinking, and strikethrough through a flag bitmask. Check the bundle's config tree (bin/console config:dump-reference <alias>) and enable what your content needs — these are off by default and their absence is a common "why doesn't my table render" complaint.
Test the parser boundary. Assert on output HTML for a handful of representative inputs (headings, code fences, links, raw HTML). This gives you a regression suite you can run against a replacement parser when you migrate.
The native extension is the whole ballgame. Sundown upstream has been unmaintained since ~2014, and the PECL bindings generally will not compile against modern PHP (7.4+, and certainly not 8.x). If pecl install sundown fails, this bundle is a dead end on that runtime — plan the exit before you start. The bundle's low score and zero dependents reflect exactly this.
Sundown does not sanitise. It converts markdown to HTML faithfully, including any raw <script> you feed it. Markdown is not an XSS defence. Either strip raw HTML (Sundown has an option for this) or pipe the output through HTMLPurifier before display. For user-generated content this is non-negotiable.
Autowiring will probably not work. Old bundles often don't declare services with proper type-hints, so __construct(SomeParser $p) may fail to resolve. Fall back to the container id you found in debug:container, or alias it explicitly in your services config.
Debugging checklist: (1) is sundown in php -m? (2) does the service appear in debug:container? (3) is output escaped — try |raw? (4) is your table/code-fence extension flag actually enabled? Those four cover nearly every failure mode.
Performance is a strength, ironically. The C parser is fast — much faster than pure-PHP parsers. If you're doing high-volume rendering and can run the extension, that's the one genuine argument for this package over CommonMark.
Migration target. league/commonmark is the maintained, spec-compliant, actively developed option with a comparable API shape ($converter->convert($md)). Parsedown is the lighter-weight alternative. Because the extension dependency is fragile, treat this bundle as a stopgap or an archival reference rather than a long-term dependency — but the wrapper pattern above makes leaving cheap.
How can I help you explore Laravel packages today?