Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Commonmark Blade Block Laravel Package

ryangjchandler/commonmark-blade-block

CommonMark Blade Block is a CommonMark extension for Laravel that lets you render Blade templates directly inside Markdown. Add dynamic, component-driven content to docs, emails, and articles while keeping authoring clean and familiar.

Deep Wiki
Context7

Getting Started

Install the package via Composer:

composer require ryangjchandler/commonmark-blade-block

Start by reading the package's README.md and then the src/ directory — the two files you care about are the extension class (typically something like BladeBlockExtension) and whatever renderer/node class it wires up. If a Laravel service provider ships with it, it will be auto-discovered and you'll see it in composer.json's extra.laravel.providers.

Minimal standalone wiring with league/commonmark:

use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\MarkdownConverter;
use RyanChandler\CommonmarkBladeBlock\BladeBlockExtension;

$env = new Environment([
    'html_input' => 'allow', // you almost certainly need this — see Gotchas
]);

$env->addExtension(new CommonMarkCoreExtension());
$env->addExtension(new BladeBlockExtension());

$converter = new MarkdownConverter($env);
echo $converter->convert($markdown);

Your first real use case: content where non-engineers write markdown but the output needs dynamic pieces — onboarding docs with the user's name, release notes that link to the current deploy, CMS pages that pull in a Blade component. Write the prose in markdown, drop a Blade block in for the dynamic bits.

Implementation Patterns

Wrap the converter in a service. Don't build the Environment inline in controllers. Bind a MarkdownRenderer singleton in a service provider so the environment is constructed once per request, extensions registered once, and the converter reused. This also gives you one place to change config.

Cache the rendered HTML, not the markdown. Markdown parsing is cheap; Blade compilation is less so. Cache the final HTML keyed on (markdown hash, user id or other context) — the inclusion of user ID in the key is important, because Blade blocks frequently reference auth()->user().

Compose with other CommonMark extensions. Register order matters: put CommonMarkCoreExtension first, your Blade block extension after it, and any other custom extensions in whatever order their docs require. Tablestyles, attributes, and heading-permalink extensions all work fine alongside this one; just be aware they all see the same event pipeline.

Use it as a Blade-adjacent rendering step. In a controller:

return view('docs.show', [
    'html' => app(MarkdownRenderer::class)->render($doc->body),
]);

Then in the view, {!! $html !!}. Do not wrap in {{ }} — the Blade block has already produced final HTML.

Testing pattern. Assert on the rendered output string, not on the AST:

it('renders a blade block', function () {
    $html = app(MarkdownRenderer::class)->render("```blade\n{{ \$name }}\n```");
    expect($html)->toContain('Taylor');
});

Pass known variables in via the rendering context so tests don't depend on auth().

Prefer Blade components in blocks over raw @php. If the package compiles the block through the normal Blade compiler, @php works but defeats the point of authoring-in-markdown. Use components from resources/views/components so authors get a stable, small vocabulary.

Gotchas and Tips

Never render untrusted markdown with this extension. Blade can call @php, @include, @auth, and anything else the compiler exposes. If markdown comes from users (comments, submissions, imported content), either strip the Blade block before parsing or run it through a sanitizer — the extension is a raw-code-execution primitive by design.

html_input and escaping. CommonMark's default is to escape raw HTML. Blade output arriving as HTML will be escaped unless you set 'html_input' => 'allow' (and typically 'allow_unsafe_links' => true if you also want to allow things like javascript: — which you shouldn't). Symptom: you see literal <div> in output.

Blocks are rendered in isolation. If the extension compiles each Blade block independently (the usual design), variables you set in one block are not visible to the next block below it. Keep related logic inside one block, or pass data in through the renderer's shared view data. Symptom: Undefined variable inside a block that clearly works if you paste it into a .blade.php file.

Blade compiler needs a bootstrapped app. In queue workers, artisan commands, or standalone scripts, app('blade.compiler') must exist before render() is called. If the package internally resolves from the container, you'll get Target [Illuminate\View\Compilers\BladeCompiler] is not instantiable when the app isn't bootstrapped — the fix is usually to call the renderer from inside the app lifecycle, not to hack the container.

Config keys are the package's, not CommonMark's. The extension will have its own config namespace (often blade_block or commonmark_blade_block). Because the repo isn't well documented externally, grep the src/ directory for getConfig / ConfigurableExtensionInterface to see exactly which keys exist before guessing.

Caching invalidation is subtler than you think. Any Blade view the block includes or component it renders can invalidate the cache. If you cache aggressively, include a hash of compiled view mtimes in your cache key, or use a short TTL and accept recompiles. Production debugging tip: drop the cache for the offending page first — half the "it renders wrong" reports are stale Blade-compiled HTML.

Double-braces collision. If your markdown is also processed by a frontend framework (Vue, Alpine) that doesn't use v-pre, {{ $name }} in the rendered output can look like a client-side binding. Either render server-side before it hits the DOM (which this package does) and make sure the compiler has actually run, or wrap live-preview output in v-pre during development.

Escape hatch. If a Blade block misbehaves and you can't tell whether it's the extension or Blade itself, take the same string, strip the fence, and Blade::render() it directly. That isolates the layer that's broken in under a minute.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator