nicolassing/quill-bundle
A Symfony bundle that integrates the Quill rich-text WYSIWYG editor into your forms and templates. It bundles the needed assets and configuration so you can add clean, customizable HTML editing to admin panels and content fields with minimal setup.
First, a framing note: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/quill.php, and no facade. A Laravel developer's realistic options are (a) you're actually working in a Symfony codebase and want to use it there, or (b) you're reading it as a reference implementation and will replicate the pattern in Laravel. Both are covered below.
Install and register (Symfony context):
composer require nicolassing/quill-bundle
Then register Nicolassing\QuillBundle\NicolassingQuillBundle in config/bundles.php and, if the bundle ships a config tree, create config/packages/nicolassing_quill.yaml. Run bin/console assets:install --symlink so the bundle's Resources/public assets land under /bundles/nicolassingquill/.
Where to look first, in this order:
vendor/nicolassing/quill-bundle/DependencyInjection/Configuration.php — the actual config keys and defaults. This file is the source of truth; the README is thin.vendor/nicolassing/quill-bundle/Form/Type/ — whether a ready-made form type exists and what options it exposes.vendor/nicolassing/quill-bundle/Resources/views/ — the form theme blocks and/or Twig helper you're expected to call from your layout.Resources/public/ — the Quill build actually shipped (check the version; it will be Quill 1.x-era).First use case: a single rich-text field on an admin form. Add the bundle's editor type to your form class, include its stylesheet in your base layout <head> and its script before </body>, render the form with the bundle's form theme, and you have a Quill instance with a default toolbar. Persist the resulting HTML in a TEXT column.
Laravel equivalent (the pragmatic path): npm i quill, import it in resources/js/app.js, build a Blade component wrapping a <div> plus a hidden <input>, and sync quill.root.innerHTML into the hidden input on submit. You get the same result without a stale, 9-star dependency.
Form-type pattern. The bundle's value is that it packages Quill as a Symfony form type, so validation, {{ form_row() }}, and error rendering work normally. Use it like any other type; override toolbar behavior through the type's options if exposed, otherwise fall back to a small JS snippet after DOMContentLoaded.
Asset inclusion. Bundles of this vintage typically expect either Assetic or a plain asset('bundles/nicolassingquill/...') reference. If you're on Webpack Encore or Vite (the modern default), don't fight it — copy the Quill dist files into your own asset pipeline and drop the bundle's asset registration. You keep the form type, you lose the coupling to an obsolete asset strategy.
Decide the storage format on day one. Quill natively speaks Delta (a JSON op list) but quill.root.innerHTML gives you HTML. Pick one:
TEXT/LONGTEXT, sanitize server-side before persisting. Simpler to render, easier to search.JSON column, re-hydrate with quill.setContents(). Lossless round-tripping, better for collaborative/undo semantics, but you need a Delta→HTML renderer for read-only views.Mixing the two halfway is the single most common source of "my formatting disappears on edit" bugs.
Sanitization belongs on the server. Quill's editor-side output is not a security boundary. In Symfony, pipe through html-sanitizer; in Laravel, mews/purifier or symfony/html-sanitizer via a form request's prepareForValidation.
Image handling. Quill's built-in image button base64-encodes into the document. That's fine for a demo and terrible for a database. Register a custom toolbar handler that POSTs to an upload endpoint and inserts the returned URL:
toolbar: { handlers: { image: () => uploadAndInsert(quill) } }
Laravel-style wrapper (if porting the pattern). A Blade component with an Alpine or vanilla-JS initializer, @vite for the assets, and a hidden input synced on submit. Keep the Quill instance in a registry keyed by field name so multiple editors on one page don't collide.
Multiple editors per page. The bundle (and most naive wrappers) assume one instance. If you need several, initialize with document.querySelectorAll('[data-quill]') and give each its own hidden input, rather than relying on a global.
Maintenance risk is the headline. Last release January 2021, 9 stars, 0 dependents, score 0.195. Treat it as unmaintained: pin the exact version, expect no Symfony 6/7 compatibility fixes, and plan for the possibility that composer update breaks you silently. For a new project, vendor the ~200 lines you actually need rather than taking the dependency.
Empty editor produces <p><br></p>. Not an empty string. Your required validation will pass on a visually empty field. Normalize before validating:
$value = trim(strip_tags($html, '<img><a>')) === '' ? null : $html;
Delta vs. HTML mismatch. If you save HTML but try to reload with setContents() (or vice versa), you get either a blank editor or mangled markup. Consistently pick one and write the corresponding getter/setter pair once.
Asset path breakage. After assets:install, verify the actual URL — it's usually /bundles/<lowercased-bundle-name>/. A 404 on quill.snow.css shows up as an unstyled, full-width textarea, which people misdiagnose as a JS failure. Check the Network tab first.
Toolbar config is often hardcoded. If Configuration.php doesn't expose a toolbar key, don't patch vendor code. Post-initialize:
quill.getModule('toolbar').addHandler('image', myHandler);
or rebuild the toolbar with quill.getModule('toolbar').container.innerHTML = ... before init.
Extension points, in order of preference:
templates/form/quill_layout.html.twig.services: Nicolassing\QuillBundle\...: decorates: ... — if the type is a registered service you can wrap it without touching vendor.Debugging checklist:
bin/console debug:container | grep -i quill # is the form type registered?
bin/console debug:twig | grep -i quill # is the form theme / extension loaded?
bin/console assets:install --symlink # did assets actually land?
Then confirm in the browser that the Quill global exists (window.Quill) before the init script runs — script ordering is the second most common failure after asset 404s.
The Laravel verdict. For a Laravel app today, npm i quill plus a ~40-line Blade component beats this bundle on every axis: maintained upstream, Vite-native, no Symfony DI to reason about, and you control the Quill version. Use this package only if you've inherited a Symfony app already depending on it — and even then, budget for replacing it at the next major framework upgrade.
How can I help you explore Laravel packages today?