bbs-lab/nova-toast-ui-editor-field
A Laravel Nova field that brings the Toast UI Editor into your admin panel, giving users a rich markdown and WYSIWYG editing experience with live preview, so long-form content is easier to write and manage.
bbs-lab/nova-toast-ui-editor-field wraps the Toast UI Editor (Markdown + WYSIWYG) as a Laravel Nova form field. It is a thin Nova field, not a content pipeline — you get an editor in the form, and whatever the user typed lands in the column as-is.
Install and wire up:
composer require bbs-lab/nova-toast-ui-editor-field
php artisan nova:publish # only if you plan to customise views/assets
The service provider is auto-discovered; there is nothing to register.
First use case — a Markdown body on a Post resource:
use BbsLab\NovaToastUiEditorField\ToastUiEditor;
public function fields(NovaRequest $request): array
{
return [
Text::make('Title')->rules('required', 'max:255'),
ToastUiEditor::make('body')
->rules('required')
->help('Markdown supported — headings, tables, code fences.')
->height('520px')
->stacked(),
];
}
Where to look first, in order:
README.md — the option list (initialEditType, previewStyle, height, toolbarItems, usageStatistics) is the whole API surface; the class itself exposes almost nothing else.src/ToastUiEditor.php — confirm the exact fluent method names for the version you install; the package tracks Nova's Field API closely, so anything not listed is probably not implemented.resources/js/ (or dist/) — the Vue component that instantiates the editor. This is where you go when the editor renders but behaves oddly (mode switch, toolbar, dark mode).Version note (v2.1.0): this release only widens the Laravel constraint to include Laravel 13. No API changes, no deprecations. Pin ^2.1 and move on. The constraint that actually bites is the Nova major version — verify your Nova 4/5 install against the package's composer.json before upgrading either one independently.
Store Markdown, not HTML — and be consistent. Pick initialEditType('markdown') or ('wysiwyg') once, per field, and treat the stored column as that format permanently. Mixing modes across records means every downstream renderer needs to sniff the format. Markdown is the safer default: it diffs cleanly, survives editor swaps, and plays nicely with Str::markdown().
ToastUiEditor::make('body')
->initialEditType('markdown')
->previewStyle('tab') // 'tab' | 'vertical'
->hideModeSwitch() // lock authors into one mode
->usageStatistics(false)
Render on the front end yourself. The field is Nova-side only. Your public app needs its own renderer:
// app/Models/Post.php
protected function bodyHtml(): Attribute
{
return Attribute::get(fn () => Str::markdown($this->body, [
'html_input' => 'strip',
'allow_unsafe_links' => false,
]));
}
Fix the Nova detail/index views. By default the field shows you the raw source, which is correct for Markdown but ugly for readers. Use a display callback so detail views render while the form stays editable:
ToastUiEditor::make('body')
->displayUsing(fn ($value) => Str::markdown((string) $value))
->onlyOnDetail(),
ToastUiEditor::make('body')
->hideFromIndex()
->rules('required'),
Or, if you prefer a plain index column, Text::make('Body')->onlyOnIndex()->limit(80).
Long-form content belongs on a separate tab/panel. A 500px editor swallows the form. Put it behind Tab::make('Content') or a Panel, and pair it with ->stacked() so it takes the full row width instead of being squeezed into a two-column layout.
Trim the toolbar to the audience. The default toolbar is enormous. Editors used by marketing vs. by engineers want different surfaces:
->toolbarItems([
['heading', 'bold', 'italic', 'strike'],
['hr', 'quote'],
['ul', 'ol', 'task', 'indent', 'outdent'],
['table', 'link'],
['code', 'codeblock'],
])
Validation caveat when using WYSIWYG. max:10000 counts HTML markup, not visible characters. Either validate markdown-only, or accept a generous limit and enforce length in your renderer/reading time calculation instead.
Pair with a preview/draft workflow. Because the column is plain text, you can freely diff revisions, run a linter/word-count job on save, or generate an excerpt with Str::words(strip_tags(Str::markdown($body)), 30) in an observer — all without touching the editor.
Telemetry is on by default. usageStatistics ships enabled and pings NHN Cloud. Call ->usageStatistics(false) on every field, or set it once in a base resource/Nova::resources() helper. This is the single most common pre-launch oversight with this package.
The editor silently blanking = asset loading, not your code. If the field renders an empty box, open the browser console first. Toast UI assets may be pulled from a CDN; behind a corporate proxy, CSP, or air-gapped environment the script never loads and there is no PHP-side error. Publish the package assets and serve them locally if that's your situation.
Nova asset publishing after upgrades. Any package update (including this 2.1.0 bump) can change the compiled component. If you see stale behaviour post-composer update, run php artisan nova:publish and hard-refresh — Nova caches its assets aggressively.
Markdown stored, HTML rendered — sanitize at the boundary. WYSIWYG mode emits HTML. If authors are trusted admins, storing raw is acceptable; if any field is user-facing, sanitize on render (HTMLPurifier, or html_input => 'strip' as above). Do not sanitize on save — you lose fidelity and can't recover the original.
Image upload inside the editor is the hard part. Toast UI's editor supports a hook-based image upload, but wiring it to Laravel storage through the Nova field usually means publishing the JS component and adding a custom hooks.addImageBlobHook that POSTs to your own endpoint. Budget for this; it is not a config flag. Many teams sidestep it by keeping images as separate Nova File fields or pasting external URLs.
Dark mode and Nova 5 theme conflicts. Toast UI Editor brings its own CSS. Expect borders, code-block backgrounds, and the mode-switch button to look wrong under Nova's dark theme until you publish the views and override. Look in resources/views for the field's blade wrapper.
Testing an editor field is a browser test. Nova's feature tests can't type into a CodeMirror/ProseMirror surface. Use Laravel Dusk (or Playwright against a seeded Nova login) and target the editor container, or bypass the UI entirely in Pest/PHPUnit by asserting the model save path with a pre-built Markdown string.
Escape hatch: don't fight the field. Because it's just a text column, you can swap editors later (EasyMDE, TipTap, plain textarea) with zero data migration — provided you stayed on Markdown. That's the strongest argument for initialEditType('markdown') as your default.
Upgrade safety for v2.1.0. Laravel 13 support is additive; nothing was removed. If you're on Laravel 10/11/12 with Nova 4 or 5, this release changes nothing for you functionally. Only re-run your Nova asset publish if you take the bump, and confirm your Nova version still satisfies the package constraint — Nova and Laravel versioning move independently and the package's composer.json is the source of truth.
How can I help you explore Laravel packages today?