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

Eloquent Markdown Laravel Package

thebatclaudio/eloquent-markdown

Add Markdown support to Eloquent models with a simple cast that converts stored Markdown into HTML for display. Keep raw Markdown in the database while serving clean, presentation-ready content in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

v2.0.0 is a compatibility-only release — no new features, no deprecations beyond the platform floor. Treat the upgrade as a dependency-hygiene task, not a re-architecture.

Requirements you must already meet before installing:

Requirement v2.0.0 v1.x
PHP >= 8.3 >= 8.2
Laravel 12 or 13 10 or 11
illuminate/* ^12.0|^13.0 broader
larastan/larastan ^3.0 older
phpstan/phpstan ^2.0 ^1.x
orchestra/testbench ^10.0 ^8/^9

Install:

composer require thebatclaudio/eloquent-markdown:^2.0

Where to look first: the package README and the published config file (php artisan vendor:publish — check the service provider for the tag). The surface area is small: it's an Eloquent integration, so the two things that matter are (a) the cast/accessor you attach to a model attribute and (b) the parser configuration behind it.

First use case: a Post model with a body column holding raw Markdown written by an editor, rendered as HTML in Blade without hand-rolling a parser, escaping layer, or cache invalidation:

class Post extends Model
{
    protected $casts = [
        'body' => Markdown::class, // confirm exact cast class name in the README
    ];
}

Then in Blade, render the cast output rather than $post->body raw. Start here, confirm the output shape (string of HTML vs. an object with ->toHtml()), and only then wire it into other models.

Implementation Patterns

Store Markdown, render on read. Keep the DB column as the author's source of truth. Never store the rendered HTML as the canonical value — you lose the ability to change parser options later and you double your storage for zero gain.

Cache the render, keyed on the attribute. Markdown parsing is cheap per row but not free across an index page. If you render lists, memoise the HTML against the model's updated_at:

public function renderedBody(): string
{
    return Cache::remember(
        "post:{$this->id}:body:{$this->updated_at?->timestamp}",
        now()->addDay(),
        fn () => (string) $this->body,
    );
}

Separate the rendering boundary. Use the cast for user-authored content only. Trusted HTML (admin-authored pages, imported CMS content) should bypass the parser — Markdown renderers happily pass inline HTML through depending on config, and that's a different threat model than a comment box.

API resources. Cast the value once in the resource rather than in the controller, so both body_markdown (raw) and body_html (rendered) can be exposed deliberately:

return [
    'body_markdown' => $this->body_raw,
    'body_html'     => (string) $this->body,
];

Mail and notifications. Rendered Markdown maps cleanly onto MailMessage bodies. Watch the line-break behaviour — Markdown hard-wraps differ from the mail client's.

Testing. Assert on a handful of fixtures (headings, fenced code, links, a table), not on full document snapshots. Snapshots of rendered HTML break on every parser bump and teach you nothing. If you're a package author consuming this, put orchestra/testbench:^10.0 in require-dev and check your matrix still resolves.

Static analysis. With phpstan/phpstan:^2.0 and larastan/larastan:^3.0 now the floor, expect a level bump to surface new errors in your own code, not in the package. Re-baseline deliberately and review what the baseline hides.

Gotchas and Tips

The upgrade is all-or-nothing on Laravel 13. illuminate/* at ^12.0|^13.0 means Composer will not let you sneak this into a Laravel 11 app. If you're mid-upgrade, either stay on 1.x or bump Laravel first — don't try to interleave.

PHP 8.3 is a hard floor at install time. Composer will refuse on 8.2 with a platform error. If your CI images are pinned to 8.2, the failure appears at composer install, not at test time — easy to misread as a network or lock-file issue.

larastan 3 requires phpstan 2. Larastan 3 will not install alongside PHPStan 1.x. If you have other static-analysis extensions pinned to PHPStan 1, they must move first; this is the most likely place for a dependency deadlock in the upgrade.

Escaping. Markdown-to-HTML then Blade {!! !!} is the standard pattern and the standard XSS footgun. Confirm whether the parser permits raw HTML by default, and if it does, either disable it in config or run the output through a sanitiser. Do this before shipping, not after.

Cache invalidation. Any render cache must key on something that changes when the Markdown changes. Keying on id alone is the classic bug — edits silently don't appear. Keying on updated_at is fine until you bulk-update via query builder, which bypasses model events and timestamps.

N+1 on rendered attributes. A cast that parses on access is fine for one model, expensive for Post::all() in a table. Either paginate tightly or precompute the render in a queued job into a separate column when the corpus grows.

Querying Markdown is a trap. LIKE '%term%' against Markdown source returns matches inside code fences and link URLs, and matches nothing the user sees as text. If search matters, index the rendered text with a full-text index or Scout — don't grep the source.

Extension points to check before forking. Most Markdown integrations expose parser configuration (extensions, allowed HTML, typographer settings) through a published config file or a container binding. Confirm whether v2 changed the config shape — the release notes don't mention it, but a major bump is a cheap moment for the maintainer to have done so silently. Diff your published config/ against the package's on upgrade.

Pin the major. Given the platform jump, use "^2.0" explicitly rather than a floating constraint. Future 2.x releases should be safe, but the 1.x → 2.x boundary is exactly where an unpinned constraint bites.

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.
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
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums