kamikx / laravel-jodit
Jodit WYSIWYG editor integration for Laravel — works with Blade, Blade components, Livewire components and Filament forms
Requires
- php: ^8.3 || ^8.4
- illuminate/filesystem: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/routing: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- illuminate/view: ^11.0|^12.0|^13.0
- intervention/image: ^4.0
Requires (Dev)
- filament/forms: ^5.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
Suggests
- filament/forms: Required to use the optional Filament Jodit form field (version ^5.0).
README
A Laravel package that integrates the Jodit WYSIWYG editor through a reusable Blade component. It works seamlessly in plain Blade templates, Blade view components, Livewire components and Filament forms. The package includes a built-in server-side file browser and uploader connector, along with secure per-editor storage scopes that isolate uploaded files and browsing access for each editor instance.
Preview
Jodit Editor in Laravel Filament form
Jodit Editor File Upload Options
Jodit Editor File Browser
Jodit Editor Image Editor
Jodit Editor Visual Blocks (custom plugin)
Features
- One Blade component —
<x-jodit::editor name="content" />covers all use cases - Livewire-ready — pass
wire-modeland the editor syncs with your Livewire component - Filament-ready — custom field
JoditEditor - Secure per-editor storage scopes — assign an isolated disk and base path to each field, with encrypted scope validation preventing access above its root (documentation)
- Local assets by default — ships Jodit, js-beautify, and Ace; optional CDN URLs use an automatic local fallback (documentation)
- Fully configurable — publish the config to override defaults, CDN URLs, middleware, etc.
- Flexible toolbar buttons — supports named profiles, custom button arrays, separators, and dropdown-friendly controls like
align
Requirements
- PHP ^8.3
- Laravel ^11.0 || ^12.0 || ^13.0
intervention/image ^4.0— bundled as a dependency; powers image resize and crop features
Installation
composer require kamikx/laravel-jodit
The service provider is auto-discovered. Publish the pre-built browser assets:
php artisan vendor:publish --tag=jodit-assets --force php artisan vendor:publish --tag=jodit-views --force
No Node.js or Vite build is required in the consuming Laravel application. Optionally publish the config:
php artisan vendor:publish --tag=jodit-config
Usage
1. Ensure your layout has asset stacks
Your main layout must include the stacks that the component pushes assets into.
The default stack names are after-styles (CSS) and after-scripts (JS).
Add these to your layout if they are not already present:
{{-- In <head> --}} @stack('after-styles') {{-- Before </body> --}} @stack('after-scripts')
You can change the stack names in config/jodit.php.
2. Drop the component into any form
Plain Blade
<x-jodit::editor name="content" :value="old('content', $post->content ?? '')" />
With required + placeholder
<x-jodit::editor name="content" :value="old('content')" placeholder="Write your post here…" :required="true" />
Livewire — two-way sync
<x-jodit::editor name="content" :value="$content" wire-model="content" />
Inside a Livewire component the wrapper is automatically set to wire:ignore so Livewire's DOM diffing does not destroy the editor. Changes are flushed back to the Livewire component via the JavaScript API with a 300 ms debounce.
Disable file browser
<x-jodit::editor name="excerpt" :file-browser="false" />
Autosize (default), custom height, and connector URL
Jodit grows with its content by default. No height option is required:
<x-jodit::editor name="body" />
For a global setting, keep 'defaults.height' => 'auto' in config/jodit.php.
You may also set 'defaults.minHeight' => 300 to change the initial minimum
height without disabling autosize.
Set a fixed height for one editor when needed:
<x-jodit::editor name="body" :height="600" connector-url="{{ route('admin.jodit.connector') }}" />
3. Use the optional Filament 5 field
The package does not install Filament. If your application does not already use Filament 5, install the Forms package first:
composer require filament/forms:^5.0
Import the optional field in your Filament resource or form schema:
use KamikX\LaravelJodit\Filament\Forms\Components\JoditEditor; JoditEditor::make('content') ->profile('simple') ->disk('public') ->basePath('uploads/sales') ->extraButtons('visualBlocks') ->fileBrowser(true) ->height(800) ->columnSpanFull() ->required();
Use extraButtons() to add one or more toolbar buttons without replacing the
selected or default profile. Multiple calls are appended in order.
Using JoditEditor without filament/forms installed throws a descriptive
exception with the required Composer command. Plain Blade and Livewire usage
remain available without Filament.
Per-editor storage roots
Use disk together with base-path to give each editor its own exact storage
root. The file browser presents that path as / and cannot navigate above it:
<x-jodit::editor name="content" disk="s3" base-path="images/demo-app" />
The equivalent Filament configuration supports closures:
JoditEditor::make('content') ->disk(fn (): string => 'public') ->basePath(fn (): string => 'uploads/sales');
The older directory option remains supported. When base-path is omitted,
it is appended below the global jodit.base_path. An explicit base-path
takes precedence over directory. If user_directory is enabled, its user
segment is appended to the resulting root in both cases.
The bundled connector receives the effective disk and root through an opaque,
encrypted storage_scope. Keep require_storage_scope enabled so request
parameters cannot be changed to access another disk or root. Disable it only
temporarily for a legacy client that does not use the package components.
Component Props
| Prop | Type | Default | Description |
|---|---|---|---|
name |
string | — | <textarea name> / form field name (required) |
id |
string | jodit_{name} |
Custom HTML id for the textarea |
value |
string | '' |
Initial HTML content |
placeholder |
string | null |
Textarea placeholder |
class |
string | '' |
Extra CSS classes on the textarea |
height |
int|string | config default (auto) |
Editor height in pixels or auto |
file-browser |
bool | true |
Enable Jodit file browser / uploader |
connector-url |
string | auto from config | Override the connector endpoint URL |
disk |
string | config default | Laravel filesystem disk used by this editor |
directory |
string | null |
Subdirectory appended below the configured global base path |
base-path |
string | null |
Exact storage root for this editor; takes precedence over directory |
wire-model |
string | null |
Livewire model property to keep in sync |
required |
bool | false |
Add required attribute to the textarea |
buttons |
array|string | config default | Custom toolbar button list (see Buttons Reference) |
extra-buttons |
array|string | null |
Toolbar buttons appended to the explicit buttons or selected/default profile |
debounce |
int | 300 |
Livewire sync debounce in milliseconds |
disable-plugins |
array|string | null |
Plugin names disabled only for this editor instance |
language |
string | config default | Jodit UI language registered by Jodit or a custom language script |
Passing buttons
You can pass the buttons prop as a PHP array (:buttons=), a JSON string, or a PHP-style array string:
{{-- PHP array (recommended) --}} <x-jodit::editor name="content" :buttons="['bold', 'italic', 'underline', '|', 'link', 'image']" /> {{-- PHP-style array string (no colon prefix needed) --}} <x-jodit::editor name="content" buttons="['bold', 'italic', 'underline', '|', 'link', 'image']" /> {{-- JSON string --}} <x-jodit::editor name="content" buttons='["bold", "italic", "underline", "|", "link", "image"]' />
Use extra-buttons to extend the explicit button list or selected/default profile:
<x-jodit::editor name="content" profile="simple" :extra-buttons="['visualBlocks']" />
Common toolbar examples
Use align when you want a single alignment dropdown instead of separate left, center, right, and justify buttons:
<x-jodit::editor name="content" :buttons="['bold', 'italic', '|', 'align', '|', 'ul', 'ol', '|', 'link', 'image']" />
Use the package's richer preset when you want the full toolbar profile:
<x-jodit::editor name="content" profile="full" />
Custom languages
Register one or more standalone JavaScript language files globally. Local asset paths and absolute URLs are supported:
// config/jodit.php 'language_scripts' => [ 'js/jodit/langs/sk_sk.js', 'https://cdn.example.com/jodit/langs/another_language.js', ], 'language' => 'sk_sk,
A standalone language file registers its translations after Jodit is loaded:
// public/js/jodit/langs/sk_sk.js Jodit.lang.sk_sk = { Bold: 'Tučné', Italic: 'Kurzíva', Cancel: 'Zrušiť', Apply: 'Použiť', };
The configured language is used globally. You can also select a registered language for an individual Blade editor:
<x-jodit::editor name="content" language="sk_sk" />
Or for a Filament field:
JoditEditor::make('content') ->language('sk_sk');
Language scripts are loaded once per page, after Jodit and before custom
plugins and editor initialization. Jodit's repository files, such as
src/langs/cs_cz.js,
are source modules with internal imports and may not work directly in a
browser. Use a browser-ready build or a standalone file that registers the
dictionary through Jodit.lang.
Custom plugins
Register one or more standalone JavaScript plugin files globally:
// config/jodit.php 'plugin_scripts' => [ 'js/jodit/plugins/statistics.js', 'js/jodit/plugins/custom-buttons.js', ],
Each file must register its plugin after Jodit is loaded:
Jodit.plugins.add('statistics', { init(editor) { editor.events.on('change.statistics', () => { console.log(editor.value.length); }); }, destruct(editor) { editor.events.off('.statistics'); }, });
Plugin scripts are loaded once per page before the editors are initialized. Disable a globally registered plugin for a specific Blade editor with:
<x-jodit::editor name="content" :disable-plugins="['statistics']" />
Or for a Filament field:
JoditEditor::make('content') ->disablePlugins(['statistics']);
Configuration
Static configuration after publishing config/jodit.php
// config/jodit.php return [ ... /* |-------------------------------------------------------------------------- | Default Editor Options |-------------------------------------------------------------------------- | | Any key/value pair here is merged into the Jodit config object before | the editor is instantiated. See https://xdsoft.net/jodit/docs/ for all | available options. | */ 'defaults' => [ 'height' => 'auto', 'toolbarSticky' => true, 'toolbarButtonSize' => 'middle', 'showCharsCounter' => true, 'showWordsCounter' => true, 'showXPathInStatusbar' => true, 'hidePoweredByJodit' => true, 'defaultActionOnPaste' => 'insert_clear_html', 'beautifyHTML' => true, 'beautifyHTMLCDNUrlsJS' => [], 'sourceEditor' => 'ace', 'sourceEditorCDNUrlsJS' => [], 'sourceEditorNativeOptions' => [ 'mode' => 'ace/mode/html', 'theme' => 'ace/theme/idle_fingers', 'wrap' => true, ], // Example: Add base theme colors to pallet 'colors' => [ 'full' => ['#EBAFCD', '#C8AA82','#6491C8','#B4EBFA','#E65A37','#c74e30'] ], // Example define custom fonts(1) 'controls' => [ 'font' => [ 'list' => [ '' => 'Default', 'Musette,sans-serif' => 'Musette', 'Panton-Light,sans-serif' => 'Panton-Light', 'Panton-Regular,sans-serif' => 'Panton-Regular', 'Panton-Bold,sans-serif' => 'Panton-Bold', 'Panton-SemiBold,sans-serif' => 'Panton- SemiBold', 'Panton-ExtraBold,sans-serif' => 'Panton-ExtraBold' ], ], ], ], /* |-------------------------------------------------------------------------- | Atomic Editor Options |-------------------------------------------------------------------------- | | Dot-notated paths listed here are wrapped in Jodit.atom() before the | editor is instantiated. Atomic values replace Jodit's built-in value | instead of being merged with it. | */ 'atomic_options' => [ // Example define custom fonts(1) 'controls.font.list', ], ... ];
Dynamic configuration with Vite assets
When language files, editor styles, or custom plugins are built by Vite, their
final URLs contain generated filenames and cannot be stored directly in
config/jodit.php. Resolve them from the Vite manifest in custom service provider
JoditEditorServiceProvider and set the Jodit configuration at runtime:
<?php // bootstrap/providers.php return [ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\Filament\AdminPanelProvider::class, ... App\Providers\JoditEditorServiceProvider::class, ];
<?php // app/Providers/JoditEditorServiceProvider.php namespace App\Providers; use Illuminate\Foundation\Vite; use Illuminate\Support\ServiceProvider; class JoditEditorServiceProvider extends ServiceProvider { private bool $configured = false; public function boot(Vite $vite): void { // The callback is triggered before rendering jodit component or jodit filament field View::composer([ 'jodit::components.editor', 'jodit::filament.forms.components.jodit-editor', ], function () use ($vite): void { if ($this->configured) { return; } // Set iframe mode config()->set('jodit.defaults.iframe', true); // Set custom iframe css config()->set('jodit.defaults.iframeCSSLinks', [ $vite->asset('resources/assets/scss/jodit-editor-content.scss'), ]); // Set custom plugin JS config()->set('jodit.plugin_scripts', [ $vite->asset('resources/assets/js/jodit/plugins/visual-blocks/visual-blocks.js'), ]); $this->configured = true; }); } }
Make sure every path passed to $vite->asset() is included in your Vite build
inputs, either directly or through an imported dependency. The example enables
iframe mode to prevent conflicts between application styles (for example,
Tailwind CSS and Bootstrap) and loads custom jodit-editor-content.scss styles inside the iframe.
Registering the connector under a custom route
If you want the connector to live under your admin prefix with your own middleware, disable the package route and register it yourself:
// config/jodit.php 'route' => [ 'enabled' => false, ],
// routes/web.php use KamikX\LaravelJodit\Http\Controllers\JoditConnectorController; Route::middleware(['web', 'auth', 'role:admin']) ->prefix('admin') ->group(function () { Route::any('jodit-connector', [JoditConnectorController::class, 'handle']) ->name('backend.jodit.connector'); });
Then tell the component which route to use:
<x-jodit::editor name="content" connector-url="{{ route('backend.jodit.connector') }}" />
Or set a global default in config/jodit.php:
'route' => [ 'enabled' => false, 'name' => 'backend.jodit.connector', // used by component when no connector-url prop ],
Buttons Reference
Use any of the names below in your buttons array. Use | as a visual separator between groups.
Text Formatting
| Name | Description |
|---|---|
bold |
Bold |
italic |
Italic |
underline |
Underline |
strikethrough |
Strikethrough |
superscript |
Superscript |
subscript |
Subscript |
eraser |
Clear formatting |
Alignment
| Name | Description |
|---|---|
align |
Alignment dropdown (left, center, right, justify) |
left |
Align left |
center |
Align centre |
right |
Align right |
justify |
Justify |
Lists & Indentation
| Name | Description |
|---|---|
ul |
Unordered list |
ol |
Ordered list |
indent |
Increase indent |
outdent |
Decrease indent |
Block / Typography
| Name | Description |
|---|---|
paragraph |
Paragraph / Headings (H1–H6) |
font |
Font family |
fontsize |
Font size |
brush |
Text colour & background colour |
classSpan |
Apply CSS class to selection |
Insert
| Name | Description |
|---|---|
link |
Insert / edit hyperlink |
image |
Insert image |
video |
Insert video (embed) |
file |
Insert file link |
table |
Insert table |
hr |
Horizontal rule |
symbols |
Special characters |
Clipboard & History
| Name | Description |
|---|---|
undo |
Undo |
redo |
Redo |
cut |
Cut |
copy |
Copy |
paste |
Paste |
selectall |
Select all |
View / Utility
| Name | Description |
|---|---|
source |
Toggle HTML source view |
fullsize |
Toggle fullscreen |
preview |
Live preview |
print |
|
find |
Find & replace |
spellcheck |
Spell check |
speech |
Speech recognition |
Separators
| Name | Description |
|---|---|
| |
Vertical separator bar |
\n |
Line break (start a new toolbar row) |
Example — compact toolbar:
'buttons' => [ 'bold', 'italic', 'underline', 'strikethrough', 'eraser', '|', 'ul', 'ol', '|', 'paragraph', 'brush', '|', 'link', 'image', '|', 'undo', 'redo', ],
Example — full editing toolbar:
'buttons' => [ 'source', '|', 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'eraser', '|', 'paragraph', 'font', 'fontsize', 'brush', 'classSpan', '|', 'align', '|', 'ul', 'ol', 'indent', 'outdent', '|', 'cut', 'copy', 'paste', 'selectall', '|', 'link', 'image', 'video', 'file', 'table', 'hr', 'symbols', '|', 'undo', 'redo', '|', 'find', 'spellcheck', 'speech', 'preview', 'print', 'fullsize', ],
File Manager Backends
The file_manager.backend config key controls which file manager is wired up when file-browser="true" (the default).
builtin (default)
Uses the package's own connector controller. No extra packages required.
// config/jodit.php 'file_manager' => [ 'backend' => 'builtin', ],
custom
Point the editor at any server-side connector that speaks Jodit's filebrowser protocol. Pass the URL via the component's connector-url prop, or set route.name in the config:
<x-jodit::editor name="content" connector-url="{{ route('my.connector') }}" />
License & credits
This project is licensed under the MIT License.
It is an independently maintained fork of nasirkhan/laravel-jodit and builds upon the work of its original authors and contributors.





