lemaur/markdown
A Laravel/PHP package for converting Markdown into HTML, offering a straightforward API for rendering documentation, blog posts, and other content. Easy to integrate into Laravel applications with configurable Markdown rendering options.
This is a thin Laravel bridge around league/commonmark that lets you treat Markdown files as first-class Laravel views — you write resources/views/docs/intro.md, and view('docs.intro') renders it through the normal view pipeline, with the compiled output cached like a Blade template.
Minimal path in:
composer require lemaur/markdown — the service provider is auto-discovered; no manual registration on Laravel 9+.php artisan vendor:publish --tag=markdown-config to get config/markdown.php in your app. Read this file first; it is the whole public surface of the package (CommonMark options, extension list, and any rendering flags).resources/views/docs/intro.md with plain Markdown, then hit it from a route: Route::get('/docs/intro', fn () => view('docs.intro'));.storage/framework/views/ — that tells you the view engine is actually wired up and not silently falling through to a 404/View [docs.intro] not found.First real use case: static content pages (terms, help centre, onboarding docs) and plain-text-ish transactional emails where you want the copy in Markdown under version control rather than HTML strings buried in a Mailable. Render the Markdown to a string, then embed it in your existing Blade layout.
Markdown view + Blade shell. Don't try to get layout inheritance out of the Markdown file itself. Keep Markdown as a content fragment and wrap it:
// PageController
return view('layouts.app', [
'content' => view('docs.intro')->render(),
]);
Then in layouts/app.blade.php: {!! $content !!}. This keeps your nav/footer/SEO in Blade where it belongs and your copy in Markdown.
Data into Markdown views. Because it renders through the view factory, data passed to view('mail.receipt', $payload) follows the same conventions as Blade. Keep the Markdown data flat and scalar — arrays and objects force you to reason about interpolation escaping inside the CommonMark pipeline, which is the least predictable part of this package.
Mailables. Render in build() and hand the HTML to HtmlString, so MJML/Blade email layouts stay reusable:
public function build()
{
return $this->subject('Your receipt')
->html(view('mail.receipt', $this->data)->render());
}
This is the pattern that pays for the package: marketing/copy edits become Markdown diffs, not PHP diffs reviewed by a developer.
Cache the rendered result, not the view. Markdown parsing is cheap per page and expensive across a large docs tree or a high-traffic endpoint. Wrap it in Cache::remember("md:{$path}:".md5($updatedAt), $ttl, fn () => view($path)->render()) and key the cache on the source file's filemtime so a deploy invalidates it naturally. Prefer that over relying on the compiled-view cache, which you cannot selectively bust.
Extensions via config, not code. Any CommonMark extension (tables, autolinks, footnotes, attributes) is enabled declaratively in config/markdown.php. Keep them there — a committed config diff is the only reliable record of what syntax your content team is allowed to use, and it's what your tests should assert against.
Testing. $this->get('/docs/intro')->assertSee('...') on the rendered HTML is worth more than unit-testing the compiler; the package's own 4.0.2 fix was a compiled-view lifecycle/caching bug, which is exactly the class of failure that only shows up when you render two different documents through the same engine in one process. Do that in at least one test: render doc A and doc B in the same test method and assert both outputs are correct and distinct.
4.0.2 is a security release — treat it as mandatory. The minimum league/commonmark is now 2.10.2. Everything up to 2.10.1 carries several advisories: quadratic-time and other denial-of-service issues, an on* attribute filter bypass in AttributesExtension, and a DisallowedRawHtml bypass. Rendering output is unchanged, so the upgrade is a drop-in — but a pinned constraint or a stale composer.lock will happily keep you on a vulnerable version. After upgrading, run composer show league/commonmark and verify you're on ≥ 2.10.2 and not on a 2.10.1-satisfying constraint.
The advisories are only theoretical until you render untrusted Markdown. Static docs you wrote yourself are low risk; a comments field, user bio, wiki page, or any Markdown pulled from the database and rendered server-side is a genuine DoS vector on the pre-2.10.2 versions. If you have that, upgrade and audit in the same pass.
Don't rely on AttributesExtension or DisallowedRawHtml alone for sanitisation. The bypass advisories are fixed in 2.10.2, but the pattern is a warning: HTML filtering via CommonMark config is defence-in-depth, not a sanitiser. For user-submitted content, render to HTML and pass the result through an allow-list sanitiser, or set html_input => 'strip' / allow_unsafe_links => false and accept the reduced feature set.
Stale compiled views are the number-one "it's broken" report. Markdown changes go through the compiled view cache in storage/framework/views. Edits that don't appear after a deploy almost always mean a stale compiled file or an opcache issue, not a broken package. php artisan view:clear first, then re-check. Add it to your deploy script if you render Markdown from routes rather than just in mailables.
Config changes need a cache reset too. config:cache in production means editing config/markdown.php has no effect until php artisan config:clear / re-cache. Same trap as any Laravel config, but here it manifests as "my new extension isn't working," which sends people hunting in the wrong place.
Naming collisions with Blade. A .md and a .blade.php file sharing a view name is resolved by whichever the engine registers first — behaviour you don't want to debug. Adopt a convention (e.g. all Markdown under resources/views/markdown/ or a .md suffix in the view name) and stick to it.
Escaping is not Blade's. Markdown views do not give you {{ }} semantics; HTML in the output is inserted raw by design. If you interpolate user data into a Markdown view, assume it lands unescaped in the final HTML and sanitise at the data boundary.
PHPUnit 13 support landed in this release. If you're on PHPUnit 13, you can upgrade cleanly; if your suite is pinned to an older major, nothing changes for you, but keep an eye on transitive dev-dependency conflicts when bumping.
Extension points worth knowing: the CommonMark Environment/Converter configuration exposed through the published config, and the view-engine registration in the service provider if you need to override compilation (e.g. to inject a custom renderer or pre-process front-matter). Front-matter is not handled by CommonMark itself — if you need it, strip and parse it before the Markdown engine sees the file, and keep that logic in one place rather than scattered across controllers.
How can I help you explore Laravel packages today?