riodwanto/filament-ace-editor
A Filament form field that integrates the Ace code editor, letting users edit code, JSON, and markup with syntax highlighting, themes, and configurable editor options inside Laravel admin panels.
Install with Composer and let Filament's package discovery pick up the service provider:
composer require riodwanto/filament-ace-editor
php artisan filament:assets # if the package ships publishable assets
Where to look first, in order:
AceEditor::make() in a resource or custom page form — this is the entry point. The field class is the real documentation; open vendor/riodwanto/filament-ace-editor/src/ and scan the public methods (mode, theme, height, etc.).First use case — drop it into any form that edits a string of code:
use Riodwanto\FilamentAceEditor\AceEditor;
AceEditor::make('template')
->label('Blade template')
->mode('html')
->theme('monokai')
->height('400px')
->required();
Anything backed by a text/longText column (JSON blobs, config snippets, CSS, SQL, email templates) is the natural target. Start with a plain mode + theme, save, reload, and confirm the value round-trips before adding bells and whistles.
Persist as a plain string column. The field is just a textarea replace — no JSON casting, no special column type. That makes it trivially testable and portable: $model->template = $data['template'] and move on.
Set mode and theme per field, not globally. Different resources want different languages. Build a small helper or enum of (mode, theme) pairs so the same editor behaves consistently across resources:
protected function editor(): AceEditor
{
return AceEditor::make('sql')
->mode('sql')
->theme('tomorrow_night')
->height('500px')
->wrap(true);
}
Pair with Filament's ->formatStateUsing / ->dehydrateStateUsing when the stored value is encoded (e.g. a PHP array cast to JSON). Format on load, decode on save, and keep the editor itself dealing with raw text only.
Use it in modals and custom pages too. Because it extends Filament's Field, it works in Action::make()->form([...]) and in custom Page classes with InteractsWithForms. This is the reason to prefer it over embedding Ace manually — you inherit Filament's validation, visibility, and reactivity.
Conditional display via normal Filament API. ->visible(fn ($get) => $get('type') === 'code') works, but if the editor is hidden then re-shown, assume the Ace instance must re-mount — see gotchas.
Read-only as a viewer. ->disabled() (or the package's readonly equivalent) turns it into a syntax-highlighted code preview inside a resource view page — often more useful than the editing mode itself.
Keep the editor value as the single source of truth. Don't also write to a hidden input or a second field; let Livewire bind to the one property and debounce the sync.
Verify Filament version compatibility first. A small package with one recent release is the classic profile for "works on v3, breaks on v4." Check composer.json for the filament/filament constraint, and install in a scratch branch before adding it to a production panel.
Livewire re-renders can clobber editor state. If the field is inside a form with reactive fields (->live() on a sibling), a re-render can reset Ace's DOM. If you see content vanish or the cursor jump on unrelated field changes, wrap the field container in wire:ignore or move the reactive field out of that subtree.
Sync timing matters. Depending on the view, Ace may push its value to Livewire on blur or debounce. Always click out of the editor (or trigger the debounce) before saving, and test with validation errors — the error re-render is the most common lost-content scenario.
Dark mode is on you. Filament's dark theme doesn't automatically switch the Ace theme. Either pass a theme per mode via ->theme(fn () => filament()->getCurrentPanel()->hasDarkMode() ? 'tomorrow_night' : 'textmate') (if the field accepts a closure — check the signature) or pick a neutral theme.
Assets aren't guaranteed. If the editor renders as a bare textarea, the Ace JS/CSS didn't load. Run php artisan filament:assets, check ->viteTheme() conflicts, and confirm the package's AceEditorServiceProvider is registered (Filament discovers it, but only if package discovery isn't disabled).
Autocompletion needs a matching mode. Ace's autocomplete is mode-provided. mode('javascript') gives you identifiers; mode('text') gives you nothing. Set both deliberately.
Publish views if you need to touch behavior. Small packages rarely expose every Ace option as a fluent method. If ->setOption()-style passthrough isn't available, publish the Blade view (php artisan vendor:publish --tag=...) and add the editor.setOption(...) call yourself — but pin the version, since upstream changes will conflict.
No rich preview, no HTML sanitization. It is a code editor, not a Markdown renderer. If the field's content is user-facing and HTML, sanitize on output; the editor will happily let an author type <script>.
Debugging tip. In the browser console, ace.edit(document.querySelector('.ace_editor')) (adjust selector) gives you the instance to inspect getValue(), getSession(), and confirm the mode actually loaded. When the value looks wrong on save, this tells you within seconds whether it's an Ace problem or a Livewire binding problem.
Extension point to keep in mind. Because it wraps Ace, anything Ace supports (folding, useWorker, custom snippets) is reachable through the underlying instance even if the PHP API doesn't expose it — treat "missing feature" as "publish the view" rather than "can't be done."
How can I help you explore Laravel packages today?