ardenthq/nova-enhanced-markdown
A Laravel Nova field for enhanced Markdown editing and rendering, bringing a richer authoring experience to Nova resources with improved previews, formatting support, and seamless Markdown content management.
nova-enhanced-markdown is a Laravel Nova field package (not a rendering library) — it swaps the default textarea for a full markdown editor with toolbar, preview, and optional image uploads. It assumes you already have a working Nova installation (v4/v5) and a resource whose model has a text column for the markdown source.
composer require ardenthq/nova-enhanced-markdown
No migrations, no service provider registration, no publish step is required for basic use. Look first at the package README's Field Options table and at src/EnhancedMarkdown.php — that single class is the entire public API surface, so its make() signature and fluent setters tell you everything the field can do.
First use case — a Post resource with a body column:
use ArdentHQ\NovaEnhancedMarkdown\EnhancedMarkdown;
public function fields(Request $request)
{
return [
Text::make('Title')->rules('required', 'max:120'),
EnhancedMarkdown::make('Body')
->rules('required')
->help('Markdown supported: **bold**, _italic_, `code`, lists, links.'),
];
}
The field stores the raw markdown string in the same column your model already uses. Nothing changes on the model or the storage layer — the upgrade path from Textarea::make('Body') is a one-line swap, and you can revert it just as easily.
Pair it immediately with a detail-view renderer so you see the output, not the source:
// Post.php (model)
public function renderedBody(): string
{
return Str::markdown($this->body, [
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
}
Keep editing and rendering separate. The field is a Nova-only concern. Render markdown in your app through Str::markdown() (CommonMark) or a Blade component — never assume the field's preview pane matches your frontend's renderer. Preview is a convenience, not a contract.
Index/detail presentation. The raw markdown in an index column is noisy. Prefer one of:
EnhancedMarkdown::make('Body')
->hideFromIndex(),
// or, if you want a column:
EnhancedMarkdown::make('Body')
->displayUsing(fn ($value) => Str::limit(strip_tags(Str::markdown($value)), 80))
->onlyOnIndex(),
Long-form content split across fields. A common layout is a short excerpt (plain Textarea) alongside body (EnhancedMarkdown). Keep the excerpt plain so search and meta descriptions stay predictable; let only the body carry rich syntax.
Translatable content. Compose with nova-translatable when you need per-locale bodies, keeping one column per locale:
EnhancedMarkdown::make('Body')
->rules('required')
->translatable() // requires optimistdigital/nova-translatable
Conditional fields. Because the editor mounts client-side, ->dependsOn() / ->onlyOnForms() work normally, but avoid mounting the field inside a collapsed panel that re-renders on every parent change — the editor instance is recreated and in-flight content can be lost. If you hit this, move the field to its own panel.
Sanitisation boundary. Treat everything the editor produces as untrusted. If you need to allow raw HTML in posts (an editorial team, say), sanitise on save — an Attribute mutator or a FormRequest::passedValidation() hook — not on render. Doing it at render time means every consumer must remember to sanitise.
Version pinning. This release (2.2.2) is a Nova compatibility fix, which tells you the field tracks Nova's internal field-component API closely. Pin the minor version in composer.json ("ardenthq/nova-enhanced-markdown": "~2.2.2") so a routine composer update in CI doesn't pull a release built against a Nova major you haven't upgraded to.
The 2.2.2 fix is a reminder, not a one-off. Nova major upgrades routinely break third-party fields because the field's JavaScript is compiled against Nova's asset pipeline. After any nova version bump, smoke-test the editor's toolbar, preview, and image upload before shipping. If the editor renders as an empty box or the toolbar is missing, the first suspect is always a Nova/field version mismatch — check composer show nova-enhanced-markdown against the release notes.
Stale compiled assets. After upgrading the package, run php artisan nova:publish (and a hard browser refresh). Nova caches its compiled assets, and a half-updated field manifests as a broken toolbar or a JS console error referencing a missing chunk.
XSS via CommonMark defaults. Str::markdown() in Laravel sets html_input => 'strip' by default, but League\CommonMark\CommonMarkConverter used directly is permissive. Explicitly pass 'html_input' => 'escape' or 'strip' and disable unsafe links. Markdown's inline-HTML capability is the single most common way this field becomes a stored-XSS vector.
Validation runs on the raw string. max:255 counts markdown syntax characters (**, [](), URLs), not rendered words. Set limits generously — max:20000 for a body column — and size the DB column (TEXT/LONGTEXT) to match. A VARCHAR(255) body column is a silent truncation waiting to happen.
Search and sorting. The column holds raw markdown, so WHERE body LIKE '%term%' matches on syntax too. If search quality matters, add a derived plain-text column (populated in a model observer) and search that instead of the source.
File uploads and disk config. Image upload support depends on Nova's storage config (config/nova.php, disk). Uploads succeeding in local dev but 403ing in production usually means the public disk symlink (php artisan storage:link) is missing or the S3 bucket policy differs — not a field bug.
Extension points are thin by design. Expect a small set of fluent setters rather than a plugin system. If you need a custom button or a different editor engine, extend the field class and override its component, or publish the package's Vue component and register your own — but understand you're forking the compiled asset and will re-do that work on every Nova upgrade. For most teams, the cheaper path is a separate plain Textarea for the exotic case rather than customising the editor.
Debugging checklist, in order: (1) does the equivalent Textarea still work — isolates field vs Nova; (2) browser console for a failed JS chunk — assets/publish; (3) php artisan nova:check-license-style version output — compatibility; (4) does the value round-trip in the DB — field vs model problem.
How can I help you explore Laravel packages today?