mati365/ckeditor5-symfony
Symfony bundle that integrates CKEditor 5 into your forms. Register a ready-made form type, load the editor assets, and configure uploads or plugins with minimal setup—letting you add a modern rich text editor to any Symfony application quickly.
First, calibrate expectations: mati365/ckeditor5-symfony is a Symfony bundle, not a Laravel package. There is no service provider, no config/ckeditor5.php, no Blade directive, and no facade. As a Laravel developer you'll end up in one of two modes:
Assuming a Symfony context:
composer require mati365/ckeditor5-symfony
php bin/console ckeditor5:install
Flex auto-registers the bundle in config/bundles.php. Then drop the form type into an existing admin form — that's the first use case and the fastest way to validate the package is healthy:
use Mati365\CKEditor5Symfony\Form\Type\CKEditor5Type;
$builder
->add('title')
->add('body', CKEditor5Type::class);
{{ form_row(form.body) }}
Where to look first: the bundle's Form/Type/CKEditor5Type.php and DependencyInjection/Configuration.php. Those two files enumerate every option the editor accepts and explain how assets are wired. Confirm the exact FQCN against the README before you paste it — small bundles rename things between versions. If your app already uses Webpack Encore or Vite, ignore ckeditor5:install entirely and point the bundle at your own build (see Gotchas).
For a Laravel equivalent, the shape is: a Blade component <x-ckeditor name="body" :value="old('body')" /> plus a Vite entry that imports ClassicEditor and mounts on a data-ckeditor attribute. Nothing more exotic than that.
Per-form config via options. The form type maps options 1:1 onto CKEditor 5's JS config object. Keep sane defaults in one place (a service or a shared array), override per field:
$builder->add('body', CKEditor5Type::class, [
'config' => [
'toolbar' => ['heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'uploadImage'],
'heading' => ['options' => [2, 3, 4]],
],
]);
Treat the editor as a textarea for validation. It submits HTML through a hidden input. Validate it like untrusted text and sanitize before persisting — paste-from-Word and image src attributes are attacker-controlled regardless of who produced the markup. In Laravel that's 'body' => 'required|string|max:65535' plus mews/purifier; in Symfony it's the equivalent constraint plus an HTML sanitizer in a form event or entity setter.
Uploads are your responsibility. The bundle renders the editor; it doesn't ship an upload endpoint you should trust blindly. Implement one:
Str::uuid(). Store outside public/ and serve through a signed route if the content is private.{ "url": "..." } for SimpleUploadAdapter, or { "urls": [...] } for multi-upload adapters.UploadAdapter in your own build, or exempt just that route from CSRF and rely on session auth. In Laravel, ->withoutMiddleware(VerifyCsrfToken::class) on that one route is the usual compromise — leave a comment explaining why.Share the toolbar config across frameworks. In a multi-framework shop, don't duplicate the toolbar in PHP and JS. Export a JSON config once, import config from './ckeditor.config.json', and build from it on both sides. The Symfony bundle and your Laravel component then stay in sync.
Testing. In Symfony, assert on the rendered hidden input and widget class — CKEditor5Type is a form widget, not a JS test. In Laravel/Pest, unit-test the upload endpoint brutally (wrong MIME, oversized, unauthenticated, filename traversal) and use Dusk or Playwright for editor behavior. Don't try to drive CKEditor through plain HTTP feature tests; it won't run.
Livewire / Inertia / Turbo. CKEditor 5 is imperative and hates DOM morphing. On Livewire, wrap the field in wire:ignore and destroy/recreate on livewire:navigated. On Inertia, tear down in onBeforeUnmount. The same pattern applies if you ever wire the Symfony bundle into a Symfony + Turbo front end — the editor instance must be explicitly disposed before the host DOM is replaced.
ckeditor5:install copies a prebuilt editor into public/. If you also have Encore or Vite building app.js, you'll load two editor copies. Pick one strategy — installer or your own npm build — and remove the other.ckeditor-duplicated-modules. Almost always two CKEditor 5 builds on the page. Same for a "plugin is missing" error when the plugin is clearly in the config. Check Network for a second ckeditor.js.{!! !!} (Blade) or |raw (Twig) deliberately, and sanitize server-side. The bundle does not sanitize on save.ckeditor.js) → bin/console debug:container | grep ckeditor → bin/console cache:clear. In a Laravel port the equivalents are the Vite build log, php artisan about, and composer show.public/bundles/..., it gets overwritten.unisharp/laravel-ckeditor (CKEditor 4) or one of the Tiptap/Livewire editors instead — CKEditor 5 in Laravel has no clear winner.How can I help you explore Laravel packages today?