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

Filament Ace Editor Field Laravel Package

jeffersongoncalves/filament-ace-editor-field

A Laravel Filament v4 field that brings the Ace code editor into your forms, with syntax highlighting plus configurable modes, themes, height, and placeholders for a rich code editing experience.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Check your Filament version before anything else. The package's version line is coupled to Filament's:

Package Filament Notes
^2.0 ^5.3 Current line; 2.0.1 raised the floor because of PageConfiguration in Page::routes(). 2.1.0 stays on ^5.3
^1.x ^4.8 Stay here if the panel is still on Filament 4

PHP 8.2+ is required either way; as of 2.1.0 the package is CI-tested on PHP 8.2/8.3/8.4 against Laravel 11/12/13. If you're on Filament 5.0–5.2, pin 2.0.0 or upgrade Filament — 2.0.1 and later will refuse to install.

2. Install and publish the config.

composer require jeffersongoncalves/filament-ace-editor-field:^2.0
php artisan vendor:publish --tag=filament-ace-editor-field-config
php artisan filament:assets

The service provider is auto-discovered; if the editor renders as a bare textarea, the assets step is almost always the missing piece.

3. Upgrading to 2.1.0? Re-publish the config — carefully. The only change that touches your app is the bundled Ace CDN default moving from 1.32.7 to 1.44.0. If you never edited base_url, you get it by re-publishing; if you did customise the config (self-hosted URL, custom themes, extension map), don't blow it away:

# see what actually changed before overwriting
php artisan vendor:publish --tag=filament-ace-editor-field-config --force
git diff config/filament-ace-editor-field.php

Then php artisan config:clear && php artisan filament:assets. Nothing else in 2.1.0 is a code change for consumers — the rest is test suite, CI matrix, changelog workflow and build tooling (bun, ace-builds/npm-run-all2 dropped from devDependencies; the published dist is unchanged).

4. Open the config first — it is the real API surface. In config/filament-ace-editor-field.php you'll find base_url, file, extensions, enabled_extensions, editor_config, editor_options, and dark_mode.{enable,theme}. Most "why isn't X working" questions are answered there, not in the component.

5. Drop it into a form. The canonical first use case is replacing a Textarea that stores raw HTML (email template, landing-page fragment, CMS block) with something a marketer can actually read:

use JeffersonGoncalves\Filament\AceEditorField\Forms\Components\AceEditorInput;

AceEditorInput::make('body_html')
    ->label('Email template')
    ->mode('html')
    ->theme('monokai')
    ->height('30rem')   // needs a CSS unit — '30' does nothing
    ->required()
    ->columnSpanFull(),

6. Read the component source once. src/Forms/Components/AceEditorInput.php is a thin wrapper (a few hundred lines) extending Filament\Forms\Components\Field; it delegates state to afterStateHydrated/afterStateUpdated and pushes options through getEditorOptions(). Skim it before you file a bug — it's faster than waiting on a 4-star, single-maintainer package.

Implementation Patterns

Establish a house component instead of repeating fluent calls. Almost every project ends up with the same three or four presets; subclass once:

namespace App\Filament\Forms\Components;

use JeffersonGoncalves\Filament\AceEditorField\Forms\Components\AceEditorInput;

class HtmlEditor extends AceEditorInput
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->mode('html')
            ->theme('github')
            ->height('28rem')
            ->editorOptions([
                'tabSize' => 2,
                'useSoftTabs' => true,
                'wrap' => true,
                'showPrintMargin' => false,
                'fontSize' => 13,
            ]);
    }
}

Then HtmlEditor::make('body_html') everywhere, with the option to override per-field. Do the same for JsonEditor, CssEditor, BladeEditor — this is the single highest-leverage pattern with this package, and it's also your upgrade insurance (the subclasses own your presets, not the vendor config).

Match the mode to what actually gets typed, and the theme to your panel. mode() and theme() auto-prepend ace/mode/ and ace/theme/, so pass 'php', 'javascript', 'json', 'css', 'markdown', 'yaml', 'blade'(if bundled) — never the fully-qualified name. Pair dark panels with an explicit dark theme:

AceEditorInput::make('rules')
    ->mode('json')
    ->theme('chrome')
    ->darkTheme('twilight');

or disable switching if your panel is light-only and you don't want the JS theme swap firing.

Store strings, not arrays — and normalize in the field. State is a plain string, which is exactly why fillForm() works in Livewire tests. If the column is JSON, keep the cast and transform at the edges:

AceEditorInput::make('config')
    ->mode('json')
    ->formatStateUsing(fn ($state) => is_array($state)
        ? json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        : $state)
    ->dehydrateStateUsing(fn (?string $state) => blank($state)
        ? null
        : json_decode($state, true))
    ->rule(fn () => function (string $attribute, $value, $fail) {
        if ($value && json_decode($value) === null) {
            $fail('This is not valid JSON.');
        }
    });

That closure rule is the closest thing to linting you get — Ace highlights, it does not validate.

Give editors a preview action rather than a second field. For HTML templates, pair the editor with an Action that renders the stored value in a modal (or a sandboxed iframe) so editors see the result without leaving the resource. Keep rendering server-side and sanitize there; the editor escapes content for display but says nothing about what you output later.

Use disabled() deliberately for detail views. The component derives a readOnly Ace flag from isDisabled(), so ->disabled() on a ViewRecord page gives a scrollable, highlighted, non-editable block — usually nicer than a TextEntry for code. Set an explicit height() for that case; read-only editors still need dimensions.

Attach Filament's normal field API without ceremony. required(), maxLength(), rules(), helperText(), live(), reactive(), visible(), columnSpanFull() all work because it's a standard Field. live(debounce: 500) is useful for a "character count / preview" sidebar; don't turn on live() without debounce on large files.

Set extensions only when you need them. addExtensions(['emmet', 'language_tools']) gives HTML abbreviation expansion and an autocomplete popup, but everything you name must exist in the config's extensions map with a resolvable URL — and after the 2.1.0 CDN bump those URLs point at a newer Ace build, so re-check any self-hosted extension copies against 1.44.0. Ship the default empty set unless a specific team asks.

Keep a test seam. Because state is a string:

livewire(EditTemplate::class, ['record' => $tpl->id])
    ->fillForm(['body_html' => '<h1>Hi</h1>'])
    ->call('save')
    ->assertHasNoFormErrors();

$this->assertSame('<h1>Hi</h1>', $tpl->refresh()->body_html);

That covers the PHP half. The Ace/Alpine half (mounting, change events, resize) only fails in a browser — reserve one Dusk/Pest-browser test per editor-heavy resource. The package's own Pest suite landing in 2.1.0 is a good sign, but it doesn't test your view layer.

Gotchas and Tips

  • Ace 1.44.0 may behave differently from 1.32.7. The CDN bump is the only runtime-visible change in 2.1.0. If you upgrade and see highlighting, scroll, or theme differences, this is why — and if you self-host, you're still on your pinned copy until you update it too. Check the console for 404s on ace/mode/* or ace/theme/* files that moved between builds.
  • Don't --force republish the config on a live app. 2.1.0 tells you to republish to pick up the new base_url; that also wipes your theme list, extension map, and editor_options defaults. Diff first, or change base_url by hand.
  • height() needs a CSS unit. ->height('200') silently produces nothing usable — the README was fixed for exactly this in 2.1.0. Use '200px', '30rem', '60vh'.
  • Double-prefixing is silent. ->mode('ace/mode/php') produces ace/mode/ace/mode/php, Ace quietly falls back to plain text, and you get a monochrome textarea with no error. Same for themes. If highlighting dies, check the string first.
  • Zero-height editors in modals, wizard steps, and tabs. Ace measures its container on mount. Fields inside a hidden step, a collapsed section, or a modal opened later commonly render at ~0px. Set an explicit height() (not autosize()) for anything that isn't visible on first paint, or trigger a resize on the tab/step-switch event.
  • CDN by default. base_url in the config points at a public Ace build. Air-gapped, corporate-proxied, or privacy-strict environments need to self-host and override both base_url and the extensions URLs — otherwise the field silently degrades to a textarea in production while working fine on a dev laptop. This is the single most likely reason you'll never take the default CDN bump.
  • Extensions not loading usually means they're not declared. enabled_extensions/extensions in the published config is the allow-list; addExtensions() only references names that map exists. Publish the config before adding extensions, not after.
  • Dark mode has two switches. dark_mode.enable in config and ->disableDarkTheme() on the component. disableDarkTheme() always wins. Also remember theme() is the light theme — setting only theme('twilight') makes it dark in both modes.
  • disabled() means read-only, not removed. Good for view pages, surprising if you expected a plain non-interactive field. Note that Ace still loads its bundle.
  • No syntax validation, no linting, no IntelliSense beyond language_tools. If you need per-language errors, validate in a custom rule or an external service. Don't promise "syntax checking" to stakeholders based on the highlighting.
  • Bundle cost is per editor instance on the page. Three Ace fields on one resource is noticeable. Prefer one field + tabs, or lazy-load via a modal.
  • Cache commands after every config or package change: php artisan filament:assets, php artisan view:clear, php artisan config:clear. The overwhelming majority of "editor disappeared after deploy" reports end here — and it's the first thing to try after the 2.1.0 upgrade.
  • Tabs vs spaces create diff noise. useSoftTabs defaults are fine for JS/CSS but set tabSize explicitly for anything stored in git-tracked template files.
  • Treat the package as forkable. 4 stars, 0 dependents, one maintainer, and a thin wrapper over Ace — which is itself in maintenance mode. 2.1.0 is a hygiene release (tests, CI, tooling), not a feature release, which reinforces the point: pin the version in composer.json (~2.1.0), keep your presets in your own subclasses, and you can swap the underlying view (or move to CodeMirror/Monaco) without touching a single form definition.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib