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

Laravel Markdown Laravel Package

jeffersongoncalves/laravel-markdown

A Laravel package for working with Markdown, making it easy to render Markdown content to HTML in your applications. Simple to install and configure, it helps keep documentation, blog posts, and other text clean and readable.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package into an existing Laravel app and publish its config before writing any rendering code:

composer require jeffersongoncalves/laravel-markdown
php artisan vendor:publish --tag=markdown-config

The config file (config/markdown.php) is the first place to look — it's where the underlying CommonMark environment, extensions, and per-extension options (heading permalinks, autolinks, tables, etc.) are declared. Everything the package does day-to-day is a thin Laravel layer over that CommonMark configuration, so most "how do I…" questions are answered by reading the config array rather than the package source.

The first real use case is almost always: a body/content column stored as raw Markdown, rendered to HTML in a controller, accessor, or Blade view.

use JeffersonGoncalves\Markdown\Facades\Markdown;

$html = Markdown::convert($post->body);
{!! Markdown::convert($post->body) !!}

Get that one path working end to end (model → convert → escaped output) before layering on extensions; nearly every other feature in the package is an extension you opt into via config.

Implementation Patterns

Render at the edge, not in the model. Keep Markdown as the storage format and convert in the controller/API resource or a Blade component. If you convert in an accessor, you lose the raw source and make it hard to re-render with different extension settings later.

// app/Http/Resources/PostResource.php
'html' => Markdown::convert($this->body),
// keep 'body' raw for editing clients
'body' => $this->body,

Cache the rendered HTML, not the Markdown. Conversion is CPU-bound and pure for a given config, so cache by content hash and invalidate on save:

$html = Cache::remember('md:'.sha1($post->body), now()->addDay(), fn () => Markdown::convert($post->body));

Bump a cache namespace/version whenever you change config/markdown.php, otherwise stale HTML survives config changes.

Compose extensions in config, not in controllers. Enable tables, footnotes, autolinks, strikethrough, and heading permalinks once in config/markdown.php. This keeps behaviour consistent between web views, API responses, emails, and queued jobs — and means a config change is the only thing you need to deploy.

Heading permalinks for docs/help-centre style content. With the heading permalink extension on, each heading gets an anchor id plus a link, which gives you a table of contents for free: render the HTML, then pull h2/h3 ids server-side (or with a tiny DOM pass) to build the sidebar. This is the pattern the v2.1.0 change touches most directly.

Multiple renderers for multiple audiences. If you render both user comments and internal docs, define two config profiles (strict/sanitised vs. permissive) and two thin wrapper services, rather than branching inside one call site.

Blade component for authored content. Wrap conversion in a <x-markdown :source="$post->body" /> component so views never call the facade directly; it gives you one place to add caching, sanitisation, or a "prose" wrapper class.

Gotchas and Tips

v2.1.0 changes the default heading permalink id_prefix to an empty string. This is the only behaviour-affecting change in the release, and it's silent: headings that previously rendered with a prefix on their id/anchor now render bare ids (#installation instead of a prefixed variant). Concretely:

  • Existing deep links, in-page TOC entries, and href="#…" anchors you generated from the old ids will break.
  • Anything that scraped heading ids (search indexing, docs sidebar caches, JS scroll-spy selectors) needs re-checking.
  • If you depend on the old behaviour, set id_prefix explicitly in your published config rather than relying on the default — pinning it makes you immune to future default changes.

Because config changes don't invalidate application caches, clear your rendered-HTML cache after upgrading.

Run php artisan config:clear when tuning extensions. Almost every "my extension config has no effect" report is a cached config file. Always re-test with config:clear before assuming the package ignores your settings.

Never render untrusted Markdown with default HTML passthrough. The underlying CommonMark environment will happily pass raw HTML through. For user-submitted content, set html_input to strip (or escape) in config and pair it with a sanitizer — Markdown is not a security boundary.

Duplicate headings collide. Permalink ids are slugged from heading text, so two ## Notes sections produce the same id. Either keep headings unique, or verify the extension's uniqueness strategy in your version before shipping a TOC that assumes unique targets.

Output is trusted HTML — mind the Blade syntax. Use {!! !!} deliberately and never on content from an unauthenticated source. In @markdown-style directives the escaping question is the same one; check which way the directive escapes before relying on it.

Config is the extension point, so treat it as code. Extension order matters when two extensions touch the same node type (e.g. permalinks + custom heading renderers). Keep an inline comment next to each enabled extension saying why it's there.

Debug at the CommonMark level. When output looks wrong, dump the environment's parsed AST (via the underlying CommonMarkConverter/Environment the package builds) rather than guessing — most surprises are parser-level, not Laravel-level.

Upgrade risk for 2.1.0 is low. The other three changes are CI pinning to commit SHAs and funding/sponsor metadata; they don't affect runtime. The only thing to review before bumping is the id_prefix default and any anchors you've already published.

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.
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules