hillrange/ckeditor
A Laravel package for integrating CKEditor into your application. It provides a ready-to-use Blade component, configurable toolbar and plugin options, image and file upload handling, and support for custom CKEditor builds, letting you add rich text editing to any form with minimal setup.
Install the bundle
composer require hillrange/ckeditor
Register it in config/bundles.php (Symfony 4 uses Flex, so this may be handled by the recipe):
return [
// ...
Hillrange\CKEditorBundle\HillrangeCKEditorBundle::class => ['all' => true],
];
Publish/install the frontend assets. CKEditor ships a large JS/CSS payload. Depending on how the bundle exposes it, either copy it to public/ or import it through Encore:
bin/console assets:install --symlink public
or in assets/js/app.js:
require('../../vendor/hillrange/ckeditor/.../ckeditor.js');
Where to look first
DependencyInjection/Configuration.php — this is the authoritative list of config keys (toolbar, height, uiColor, filebrowser settings, config_name).Form/Type/CKEditorType.php) — it defines the options you can pass per field.Resources/views/ — the Twig template that renders the <textarea> plus the JS bootstrapping.First use case — swap a plain textarea for a rich-text field in a CRUD form:
use Hillrange\CKEditorBundle\Form\Type\CKEditorType;
$builder
->add('body', CKEditorType::class, [
'config_name' => 'default',
'required' => false,
]);
Render it as usual ({{ form_widget(form.body) }}) and render the stored HTML with {{ article.body|raw }} on the read side — see the XSS gotcha below.
Named configurations instead of per-field arrays. Define toolbars once in config/packages/hillrange_ck_editor.yaml and reference them by name. This keeps templates and form classes clean and makes toolbar changes a config-only deploy:
hillrange_ck_editor:
configs:
default:
toolbar: [ ['Bold', 'Italic', 'Link'], ['NumberedList', 'BulletedList'] ]
height: 300
minimal:
toolbar: [ ['Bold', 'Italic'] ]
height: 120
$builder->add('summary', CKEditorType::class, ['config_name' => 'minimal']);
Match the editor to the content type. Article bodies get a full toolbar with source view; meta descriptions / teasers get minimal. This is the cheapest UX win and avoids authors pasting Word HTML into a 100-character field.
Injecting config for non-form contexts. For editors created outside a Symfony form (inline editing, modals), pass the serialized config from Twig rather than duplicating toolbar definitions in JS:
<textarea id="inline-body" data-ckeditor-config="{{ ckeditor_config('minimal')|json_encode }}"></textarea>
Then CKEDITOR.replace('inline-body', JSON.parse(el.dataset.ckeditorConfig)) in your entry point. Verify the exact Twig helper name in the bundle's Twig/ extension — it varies between forks.
Encore integration. Add CKEditor as a separate entry so the ~500KB payload isn't on every page:
// assets/js/editor.js
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
document.querySelectorAll('[data-ckeditor]').forEach(el => ClassicEditor.create(el));
and include <script src="{{ asset('build/editor.js') }}"></script> only in the edit/new templates — not in base.html.twig.
Server-side pipeline. Treat the textarea as untrusted input even though it's "your" admin UI:
$builder->add('body', CKEditorType::class)
->add('save', SubmitType::class);
// in the controller, after validation
$article->setBody($htmlPurifier->purify($form->get('body')->getData()));
Store sanitized HTML in a TEXT column, not a VARCHAR — CKEditor output easily exceeds 255 characters.
File/image uploads. CKEditor 4 and 5 handle uploads completely differently. If the bundle bundles CKEditor 4, you need a filebrowser connector (CKFinder or a custom upload route) configured in the filebrowser* config keys. For CKEditor 5 you must implement an upload.adapter in JS pointing at a Symfony controller returning { "url": "..." }. Budget real time for this — it is the most common "why doesn't the image button work" ticket.
This package is a low-traffic fork of an abandoned idea. Zero stars, zero dependents, and a "Symfony 4.x" scope means it has not been maintained against Symfony 5/6/7. Before committing:
Bundle class extends the now-required AbstractBundle or still uses the removed Bundle::getContainerExtension conventions.CKEditorType::getName() / getBlockPrefix() uses the deprecated 'ckeditor' string form — always reference CKEditorType::class in your form builder so you're immune either way.TextareaType with a data-ckeditor attribute. That's genuinely all most projects need.Symfony 4 itself is EOL. If you're on this bundle you're presumably on an inherited 4.x app. Pin your PHP version, and note that friendsofsymfony/ckeditor-bundle (the maintained successor to egeloen/ckeditor-bundle) is the better long-term bet if you need CKEditor 4 specifically.
CKEditor 4 vs 5 is a hard fork, not a version bump. Different APIs, different plugin ecosystems, different config syntax. Confirm which one the bundle ships before writing a single toolbar config — the YAML keys are not portable between them.
Licensing. CKEditor 4 is GPL/LGPL/MPL tri-licensed; CKEditor 5 is GPL2+ or commercial. If you're building closed-source, verify this early — it bites during legal review, not development.
Assets 404 after deploy. The classic symptom is a bare textarea with a JS console full of 404s. Fix: bin/console assets:install public in your deploy script, plus bin/console cache:clear — the bundle caches its asset paths and config.
Config changes need a cache clear. Toolbar edits in YAML will silently not apply until you bin/console cache:clear in dev. This wastes a lot of "why is my button still there" time.
Never |raw un-sanitized output. CKEditor happily pastes <script> from clipboard HTML. Purify on save (or on render, htmlpurifier bundle), and add a CSP header as a second layer.
Multiple editors on one page. Ensure each field gets a unique ID — Symfony does this by default, but if you build the editor IDs yourself in JS you'll get "editor instance already exists" errors. For repeated collections, initialize on collection:entry:added events rather than at DOMContentLoaded.
Readonly/disabled fields. CKEditor replaces the textarea, so disabled => true may render an editable-looking editor whose content is never submitted. Handle "view" pages with a plain <div>{{ body|raw }}</div> instead of a disabled editor.
Debugging checklist, in order: (1) view-source for the data-* attributes the bundle emits; (2) browser console for CKEDITOR/ClassicEditor is not defined; (3) Network tab for a missing ckeditor.js; (4) bin/console debug:config hillrange_ck_editor to confirm your YAML actually merged.
Extension points worth knowing. Most CKEditor bundles let you (a) override the Twig template via templates/bundles/<BundleName>/..., (b) register a custom form type extension to default options project-wide, and (c) merge extra JS config through a ckeditor.config event. Use (b) to set config_name defaults per form theme so developers stop repeating it on every field.
How can I help you explore Laravel packages today?