chrisreedio/filament-monaco-editor
This package adds a Monaco Editor (the VS Code editor) form field to Filament, so you can edit code, JSON, SQL, YAML or similar payloads inside a normal Filament resource form instead of a plain Textarea.
Minimum steps:
v1.0.0-beta.1 is pre-stable, so Composer needs a stability flag: composer require chrisreedio/filament-monaco-editor:"^1.0@beta". Without @beta the constraint will not resolve.php artisan filament:assets), then check vendor/chrisreedio/filament-monaco-editor/resources/ to see whether Monaco is bundled locally or pulled from a CDN. This determines whether your app works offline.longText/json column:use ChrisReedIO\FilamentMonacoEditor\...\MonacoEditor; // confirm exact namespace from the README install snippet
MonacoEditor::make('source')
->language('php')
->theme('vs-dark')
->required(),
First use case worth shipping: a Settings page or a resource where an admin edits a raw JSON config blob — ->language('json') plus validation via ->rules(['json']) gives you syntax highlighting and bracket matching immediately.
Where to look first: the README for the exact class namespace and supported methods, then src/ for the field class (language/theme/options setters), then the Blade view for how the Alpine component initialises Monaco and syncs state to Livewire.
Treat it as a normal Filament field. It dehydrates into your form state like any other component, so ->required(), ->rules(), ->maxLength() and ->dehydrated(false) all behave as expected. Wrap validation in rules that match the language (json_decode, a YAML parser, php -l via a custom rule) — Monaco highlights but does not validate.
Drive language/theme from context with closures. The useful pattern is per-record or per-tenant languages:
MonacoEditor::make('body')
->language(fn (callable $get) => match ($get('type')) {
'sql' => 'sql',
'yaml' => 'yaml',
default => 'markdown',
})
Because the closure runs server-side, pair it with ->live() on the controlling Select so the field re-renders.
Theme should follow Filament's dark mode rather than being hardcoded. Pass a closure reading the current panel mode, or set both on the JS side by watching document.documentElement.classList. Hardcoding vs-dark is the single most common visual bug report.
Pass through Monaco's own options via the options array — minimap, wordWrap, tabSize, readOnly, scrollBeyondLastLine, automaticLayout. automaticLayout => true is essentially mandatory inside Filament, since Livewire/Alpine resizing otherwise leaves the editor with a stale width.
Use it in modals, repeaters and actions. For repeaters, make sure each item produces a distinct Livewire key; otherwise morphdom reuses the container and two editors fight over one DOM node. For modals, mount on the modal's open event rather than page load.
Preview/read-only pattern: ->disabled() for the field, plus readOnly => true in the options, gives you a syntax-highlighted viewer for log or config display without a second package.
Where it beats the alternatives: use Monaco for structured code/config; keep RichEditor or a Markdown editor for prose. Don't use Monaco for anything a non-technical editor will touch.
v1.0.0-beta.1's changelog is exclusively Dependabot bumps (ramsey/composer-install 3→4, dependabot/fetch-metadata 2.5.0→3.0.0). It is a CI/packaging release — upgrading carries no behaviour, API or migration changes, but also gives you no fixes. If you were waiting on a bugfix, this tag does not provide it.composer.json and be prepared for class/method renames before 1.0.0 stable. Don't build a plugin API on top of this field's internals yet.$refresh), Monaco's DOM gets replaced and you're left with an empty box while the state is still correct. The fix is wire:ignore on the editor container plus re-initialisation on re-render — verify the shipped Blade view already does this before debugging your own code.Content-Security-Policy or an air-gapped environment silently kills the editor. Check the browser network tab first; if needed, publish/vendor the assets locally.fill(). Programmatic state changes from Livewire don't always reach the JS instance. Test $this->form->fill([...]) and reset() explicitly; you may need to $watch the Livewire property and call editor.setValue().Livewire::test(). That only asserts the dehydrated state — it says nothing about whether the editor rendered. Use a browser test (Dusk/Playwright) for anything involving the JS; keep the fast Livewire tests for validation rules.minimap and enable wordWrap for anything over a few hundred lines; Monaco is heavy and Filament admin pages already ship a lot of JS.How can I help you explore Laravel packages today?