Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Ckeditor Laravel Package

unisharp/laravel-ckeditor

Integrates CKEditor into Laravel with simple configuration, Blade helpers, asset publishing, and file upload support. Add a full-featured WYSIWYG editor to your forms quickly, with customizable toolbar and upload handling out of the box.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin Laravel wrapper that vendors the CKEditor 4 JavaScript distribution and adds Blade/config conveniences on top. The 4.7.2 release is purely an upstream asset merge — the package's PHP surface is unchanged — so existing installs keep working; you just get a refreshed ckeditor.js/plugin bundle.

  1. composer require <vendor>/<package> — then check composer show <vendor>/<package> to see whether the release tag maps to a Laravel-version constraint you satisfy.
  2. Publish the assets and config:
    php artisan vendor:publish --provider="Vendor\Package\ServiceProvider"
    
    You should end up with config/ckeditor.php (toolbar, height, extra plugins, file-browser URLs) and a copy of the editor under public/vendor/ckeditor/.
  3. Look first at config/ckeditor.php and the package's Blade view/helper — that's where 90% of day-to-day customisation happens. Then read resources/views/vendor/... to see exactly what markup is emitted (it's usually a <textarea> + an inline CKEDITOR.replace() call).
  4. First use case: drop a rich-text body field into an existing CRUD form. In Blade:
    <textarea name="body" id="body">{{ old('body', $post->body ?? '') }}</textarea>
    @ckeditor('body')   {{-- or the package's helper/directive --}}
    
    Then in the controller, validate and persist the HTML, and sanitise it on output (see Gotchas — this matters far more than usual here).

Implementation Patterns

Profile-based configuration. Don't inline toolbar arrays in every view. Define named profiles in config/ckeditor.php (simple, full, comment) and select per form. Keeps the WYSIWYG consistent and lets you tighten the toolbar where the content is user-generated.

Single reusable Blade component. Wrap the textarea + init in <x-rich-text name="body" profile="full" /> so you control id generation, old() repopulation, and error-class styling in one place. Multiple instances on one page are common (e.g. short + long description) — make the component derive a unique DOM id from name.

File uploads / browse server. The package config typically exposes filebrowserUploadUrl / filebrowserBrowseUrl. Point these at your own routes:

Route::middleware(['auth', 'can:manage-media'])->group(function () {
    Route::post('ckeditor/upload', [EditorUploadController::class, 'store']);
    Route::get('ckeditor/browse', [EditorBrowseController::class, 'index']);
});

In the upload controller, validate with a Form Request (image, mimes:jpg,png,webp, max:4096), store on a dedicated disk (Storage::disk('public')->putFile('editor', $file)), and return CKEditor's expected callback shape — either a JSON {uploaded: 1, url: ...} payload or the legacy window.parent.CKEDITOR.tools.callFunction(...) HTML. Never echo the user-supplied filename back into that script.

CSRF. CKEditor's XHR uploads don't automatically carry your token. Either add the meta tag and set it in config:

config.extraPlugins = '...';
config.filebrowserUploadMethod = 'xhr';
config.headers = { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content };

…or exclude only the upload route from CSRF (weaker — prefer the header).

Content storage and rendering. Store HTML in a text/longText column. Sanitise on render as well as on save, using mews/purifier or league/html-sanitizer, then output with {!! $sanitised !!}. Consider storing a plain-text body_excerpt derived via strip_tags() for search and card previews — saves you querying inside markup later.

Livewire / Inertia. The editor mutates the DOM behind Livewire's back. For Livewire, wire CKEDITOR.instances[id].on('change', ...) to @this.set('body', ...), and re-init in wire:ignore + Livewire.hook('morph.updated'). For Inertia, initialise in onMounted and destroy in onUnmounted to avoid leaking instances across visits.

Testing. Feature tests just assert the HTML round-trips to the DB. Cover the upload endpoint with real HTTP tests (auth, mime rejection, size limits) — that's where the security surface is. Use Dusk only for smoke-testing that the toolbar renders; don't try to drive the editor's iframe in Pest/PHPUnit.

Gotchas and Tips

4.7.2 is a 2018 build — treat the bundled editor as a security liability. CKEditor 4 reached end of life in mid-2023, and 4.7.x predates a long list of XSS/HTML-injection CVEs fixed in later 4.x releases (dialogs, protected source handling, the htmlwriter and various plugin parsers). This release tells you the package is tracking a stale upstream tag, not that it's current. Concretely:

  • Keep the editor behind auth and a capability check; don't expose it on public comment forms without sanitisation.
  • Always sanitise stored HTML server-side with an allow-list — never rely on the client-side Advanced Content Filter.
  • If you must stay on CKEditor 4, push the vendored build to the last LTS (4.22.1) or check whether the package exposes an asset-override path, and plan a migration to CKEditor 5 / TipTap for anything long-lived.

Re-publish assets after every update. vendor:publish won't overwrite existing files unless you pass --force. If the toolbar doesn't change, or you still see old behaviour after upgrading, you're almost certainly running a stale public/vendor/ckeditor/. Verify with a hash or the version in the editor's about dialog.

Browser caching. ckeditor.js is served from public/, not through Vite's manifest, so it gets no cache-busting hash. Add a ?v= query string from your config version, or set long-lived immutable headers and a versioned path, otherwise users see mixed old/new plugin code.

Publishing into public/ vs. public/vendor/. If the package publishes somewhere your deploy pipeline doesn't sync (some pipelines only sync public/build), the editor 404s in production but works locally. Add the vendor path to your deploy script or cp -r step.

Advanced Content Filter will silently eat your markup. Styles, classes, and data-* attributes get stripped unless allowed. If editors complain that their <div class="callout"> disappears, fix it in config.allowedContent / extraAllowedContent rather than disabling ACF wholesale — allowedContent = true is the classic route to a stored-XSS incident.

Inline config vs. package config. Values set in the Blade init call override config/ckeditor.php, and CKEditor's own config.js can override both. When a setting "doesn't apply," log the resolved CKEDITOR.instances[name].config in the console — it's almost always precedence, not a bug.

CSP. If you ship a strict Content-Security-Policy, the editor's inline init script and the upload callback's inline <script> will be blocked. Either allow 'unsafe-inline' for the editor routes (narrowly) or move init into a bundled JS file and use the JSON upload response rather than the script-tag callback.

Path handling in the browser/upload endpoints. Never accept a client-supplied path or filename. Generate storage names (Str::random(40) . '.' . $ext) and return the URL from Storage::url(). Also decide public-vs-private disk deliberately: a public disk means every uploaded image is world-readable by URL guess; a private disk means you need signed/temporary URLs in the browse endpoint.

Extension points to prefer. The config file and the published Blade view are the intended seams. Avoid forking the package's JS — subclass/publish instead, so the next upstream merge (like this one) is a no-op for you.

Reading the changelog. "Merge from official CKEDITOR 4.7.2 tag" means the PHP side is untouched: no new config keys, no signature changes, no deprecations. A composer update is safe; the only real action item is re-publishing assets and re-checking your sanitisation posture against a 2018-era editor.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core