helios-ag/fm-summernote-bundle
Symfony bundle that integrates the Summernote WYSIWYG editor with FM (FormManager), providing a ready-to-use form type and frontend assets for adding rich-text editing to textareas with minimal configuration.
This is still a Symfony bundle, not a Laravel package — 1.0 does not change that. There is no service provider, no config/summernote.php, and no package auto-discovery. The release notes are a Symfony-upgrade changelog (Add symfony bundle config, Symfony5 changes, Updates for sf6), not a Laravel feature drop. As a Laravel developer you still cannot meaningfully composer require this into a Laravel app: it pulls symfony/form, symfony/http-kernel, and Twig. Treat it as a reference implementation of "how to wire Summernote into a form framework."
What 1.0 does change: it is the first version that targets Symfony 5/6, and it finally ships a real bundle config tree instead of hardcoded defaults. That means there is now a config/packages/fm_summernote.yaml to configure — the thing that was previously guesswork.
If you are in a Symfony codebase (the audience this package actually serves):
composer require helios-ag/fm-summernote-bundle
Register the bundle in config/bundles.php:
FM\SummernoteBundle\FMSummernoteBundle::class => ['all' => true],
Configure it under config/packages/fm_summernote.yaml. As of 1.0 the known keys are:
base_path — renamed from basePath in this release (see Gotchas)language — new in 1.0, sets the editor UI languageinclude_jquery / include_bootstrap — asset injection togglesfront — new in 1.0, lets you supply a custom front/templateVerify key names against vendor/helios-ag/fm-summernote-bundle/README.md — the README was touched in this release but has historically lagged the code.
Render the editor assets once in your base layout, then swap a textarea for the bundle's form type:
$builder->add('body', SummernoteType::class, ['attr' => ['rows' => 10]]);
First use case: identical to before — replacing a <textarea> in an admin CRUD form with a rich-text field, storing the resulting HTML in a TEXT column, and posting images to an upload endpoint. The new language option is the only thing that meaningfully changes day-one setup.
For the Laravel equivalent, skip the bundle entirely and take the 20-minute path: npm i summernote (or the CDN build), a Blade component, and a @push('scripts') init:
{{-- resources/views/components/summernote.blade.php --}}
<textarea name="{{ $name }}" id="{{ $id }}" {{ $attributes }}>{{ $slot }}</textarea>
@push('scripts')
<script>
$('#{{ $id }}').summernote({
height: {{ $height ?? 300 }},
lang: '{{ $lang ?? 'en-US' }}',
callbacks: {
onImageUpload: files => uploadToLaravel(files, '#{{ $id }}')
}
});
</script>
@endpush
Note the parity: the bundle's new language config maps to Summernote's lang option plus a summernote-{lang}.js include, so if you replicate the feature in Blade you have to load that file yourself.
Form-type pattern (Symfony). The bundle's core value is a SummernoteType that renders the textarea and emits the init script, so validation and data binding stay inside the Form component. Options you pass as attr typically bubble down to the JS init call, which is the cleanest way to vary toolbar per form.
Asset inclusion. Symfony bundles in this family usually expose a Twig helper or rely on you including the built assets manually — now governed by include_jquery / include_bootstrap / base_path in config. The safest pattern is to publish/copy the Summernote dist into your own asset pipeline once, set include_jquery: false, and only use the bundle for the form type. That avoids double-loading if you also add Summernote via npm/Encore/Webpack. The 1.0 config makes this easier to express than 0.2 did, but it still requires you to own the decision.
Language handling. New in 1.0: set language once in fm_summernote.yaml for the whole app. Summernote ships one JS file per locale, so if you disable the bundle's asset injection you must add the matching summernote-{lang}.js to your bundle yourself — the config key alone will not fetch it.
Custom front. Also new in 1.0: front lets you point the bundle at your own template instead of the shipped init.html.twig. This supersedes the old advice about overriding templates via templates/bundles/FMSummernoteBundle/ for most cases — prefer the config key, since it survives composer update without file-tree shadowing.
Image uploads. Summernote's onImageUpload callback expects a JSON response shaped like {"url": "https://.../image.jpg"} and nothing else. Two integration options:
helios-ag) and point the callback at the elFinder connector route — this is the intended combination and the main reason people install this bundle.['url' => Storage::url($path)].Laravel version of the upload callback, with the CSRF handling that Summernote does not do for you:
async function uploadToLaravel(files, editor) {
for (const file of files) {
const data = new FormData();
data.append('image', file);
const res = await fetch('/admin/uploads', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
body: data,
});
const { url } = await res.json();
$(editor).summernote('insertImage', url);
}
}
Storage and output. Always store the raw HTML and sanitize on render, never the reverse. In Laravel that means a purifier step (mews/purifier or symfony/html-sanitizer) in an accessor or Blade component, and {!! !!} only on the purified value. Symfony users should install html-sanitizer since the form type does not sanitize anything — 1.0 adds no sanitization.
Livewire / SPA interop. Because Summernote mutates the DOM behind the framework's back, bind it explicitly: initialize on Livewire.hook('morph.updated'), and push the editor's HTML back with $wire.set('body', $('#editor').summernote('code')) on change. Otherwise Livewire re-renders will silently discard edits.
Validation. Add a required rule plus a normalizer — an "empty" Summernote field submits <p><br></p>, not '':
protected function prepareForValidation(): void
{
$this->merge([
'body' => trim(strip_tags($this->body ?? '')) === '' ? null : $this->body,
]);
}
basePath → base_path rename is the breaking change in 1.0. If you are upgrading from 0.2 and had basePath in your bundle config, the new config tree will ignore it — likely silently, leaving assets pointed at the wrong path. Grep your config/packages/ for basePath before anything else, and expect the failure mode to be "editor loads but plugins/CSS 404."bin/console config:dump-reference fm_summernote) rather than trusting the README; documentation changes in this release were a single README.md edit.$(...).summernote is not a function, which almost always means the init script ran before summernote.min.js loaded. In Laravel, @push('scripts') ordering vs. your Vite bundle is the usual culprit; make the init a module import rather than an inline script.include_jquery/include_bootstrap are on and you bundle Summernote yourself, you get two copies and confusing "plugin already registered" errors. Pick one owner of the assets — with 1.0 that decision is now a config flag, so make it explicit._token. A 419/403 on image upload with everything else working is this, 100% of the time.{url: ...} shape is strict. Returning {"location": ...} or a bare string makes uploads fail silently with no console error. Log the response body when debugging.front is the new extension point, and it is untested-by-the-community. Supplying a custom template is cleaner than shadowing templates/bundles/FMSummernoteBundle/, but it is a first-release feature with a single contributor and no docs beyond the config key. Diff your template against the shipped init.html.twig (which itself got a typo and a bracket fix in this release) so you do not inherit a bug that was just patched upstream.script, iframe, on* attributes) at the sanitizer config level.How can I help you explore Laravel packages today?