bythepixel/nova-tinymce-field
A Laravel Nova field that adds a TinyMCE WYSIWYG editor to your resources, giving users rich-text editing for long-form content. Install via Composer and attach it to any Nova resource to swap plain textareas for TinyMCE's formatting and media tools.
composer require bythepixel/nova-tinymce-field. There is no migration and usually no config to publish — this is a pure Nova form field — but confirm against the README before assuming, and check composer.json in the vendor directory for the Nova version constraint (this package's last release is April 2024, so treat Nova 4 as the safe target and verify Nova 5 compatibility yourself).Nova::script() / Nova::style() to inject the TinyMCE bundle into the Nova SPA. Read vendor/bythepixel/nova-tinymce-field/src/ first: the field class tells you which fluent setters exist (options(), height(), and any API-key/CDN helpers), and the compiled JS tells you which TinyMCE version and URL it loads.longText rather than text, since text caps at ~64KB and long articles will silently truncate under non-strict MySQL:$table->longText('content')->nullable();
use Bythepixel\NovaTinyMCE\NovaTinyMCE; // confirm exact namespace/class from the README
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')->rules('required', 'max:255'),
NovaTinyMCE::make('Content')
->rules('nullable')
->options([
'menubar' => false,
'plugins' => 'lists link code table',
'toolbar' => 'undo redo | bold italic | bullist numlist | link table | code',
'height' => 500,
]),
];
}
tinymce.min.js loads in the network tab, type some bold text, save, then tinker the model and assert the HTML persisted. Once that round-trip works, wire up frontend rendering ({!! $page->content !!} behind a sanitizer) and you have the whole pattern.NovaTinyMCE as App\Nova\Fields\RichText and put your house defaults (toolbar, plugins, height, branding off, content style) in the constructor. Every resource then uses one class, and swapping the underlying package later is a one-file change.RichText::full('Content') reads intent at the call site.config/nova-tinymce.php in your app and merge them per field, so a copy tweak or plugin addition doesn't require touching every resource. Remember config:cache in production.images_upload_url and file_picker_callback expect a route that returns { "location": "https://..." }. Nova's temporary-upload endpoint isn't shaped for this, so add a small authenticated controller that validates mime/size and stores to your configured disk. Keep it behind the same auth middleware as Nova.->fillUsing() with HTMLPurifier (mews/purifier) and an allowlist that matches the tags/classes your frontend CSS actually styles. Sanitizing at write time keeps existing rows clean and makes the stored column trustworthy for feeds and APIs.->hideFromIndex() for long bodies, and ->displayUsing(fn ($v) => Str::limit(strip_tags($v), 120)) if you want a preview — rendering raw HTML inside an index cell will wreck the card layout.->rules('nullable', 'max:65000') where you use text, plus a frontend maxlength via ->options(['max_chars' => ...]) if you need a hard cap. Pair with a validation message that doesn't confuse editors.Content (EN), Content (FR)) or wrap in nova-translatable if your version supports it; the field itself is locale-agnostic.composer.json, add the field to your Nova upgrade checklist, and keep the App\Nova\Fields\RichText wrapper so a fork or replacement is contained.api_key in options() or self-host TinyMCE and point the field at it. Check your script-src CSP allows whichever origin you use.php artisan nova:publish and php artisan view:clear after upgrades, and verify the compiled JS is being served rather than a stale published copy.tinymce.init payload. Test each override you rely on; when in doubt, pass the complete array rather than a partial one.<p> wrappers, and strips attributes not in valid_elements. If you need specific frontend classes or data-* attributes to survive, add them to valid_elements/extended_valid_elements or they will vanish on the next edit.{!! !!} unsanitized user content, and check whether the field's detail component renders HTML unescaped — if it does, your resource detail page is a vector too.skin: 'oxide-dark', content_css: 'dark'. Accept it or wire it to a user preference.content_style. Inline CSS with quotes inside a PHP array inside a JSON payload breaks easily; keep it in a single-quoted string and verify the field still initialises.tinymce is not defined; network tab for a failed script load; Vue devtools to inspect the field's meta/options payload; a tinker dump of the raw model attribute to confirm what was actually saved.options()/component() over editing vendor files. If you need a genuinely different editor later, the wrapper class is where you swap it — resources shouldn't notice.How can I help you explore Laravel packages today?