timo-de-winter/filament-monaco-editor
Filament form field that brings the Monaco Editor, the code editor behind VS Code, into your admin panels. Edit JSON, CSS, JavaScript, and other code directly in forms with syntax highlighting, autocompletion, and the familiar editor experience.
composer require timo-de-winter/filament-monaco-editor
The service provider is auto-discovered. Check what's publishable before assuming defaults are wrong:
php artisan vendor:publish --provider="TimoDeWinter\FilamentMonacoEditor\FilamentMonacoEditorServiceProvider"
Where to look first, in order:
language(), theme(), height(), plus an options passthrough), so a five-minute read covers 90% of it.vendor/timo-de-winter/filament-monaco-editor/src/ — this is the authoritative list of fluent methods for the installed minor version. The 5.x line moved from 5.0.0 to 5.6.3 across many point releases, and the published changelog is a compare link rather than per-release notes, so the source is more trustworthy than any blog post or older README snippet.First use case: swap a Textarea for a MonacoEditor on one field in one Resource. A JSON settings blob, a CSS override, a JS snippet, or an email template are the canonical candidates — anywhere line numbers, bracket matching and syntax colouring meaningfully reduce support tickets.
use TimoDeWinter\FilamentMonacoEditor\MonacoEditor;
MonacoEditor::make('custom_css')
->language('css')
->height(400)
->theme('vs-dark');
Confirm the namespace against the installed version's source before copying — package namespace casing has shifted between majors historically.
Store as a proper text column. Monaco invites multi-KB documents. TEXT/LONGTEXT/JSON columns, not string(255). If documents routinely exceed a few hundred KB, consider a side table so you don't drag the blob into every list query.
Drive language from record context. Language is usually a property of the thing being edited, not the field:
MonacoEditor::make('source')
->language(fn (?Model $record) => $record?->language ?? 'plaintext')
->height(500);
Because the closure participates in Livewire re-renders, changing a sibling Select with ->live() will swap the language without a page reload. Pair that with ->key() on the editor if you hit remount issues.
Tame the Livewire round-trip. Monaco emits on every keystroke. Default behaviour is fine for small fields; for expensive validation or large payloads, debounce and validate less eagerly:
MonacoEditor::make('query')
->language('sql')
->debounce(750)
->rules(['required', 'string', 'max:65535']);
Prefer ->live(onBlur: true) for fields where you only need server-side state on submit.
Validate with a real parser, not the editor. Monaco's squiggles are cosmetic and never block submission. Add explicit rules — json_decode in a rule(), a Rule::in for allowed language values, or a linting service in afterStateUpdated.
Read-only display. In an infolist or a disabled() form, prefer ->readOnly() over ->disabled() — read-only keeps the value in the Livewire payload for copy/export actions, whereas disabled() strips it.
Options passthrough for anything not exposed fluently. Word wrap, minimap, tab size and similar are Monaco-level concerns:
MonacoEditor::make('payload')
->language('json')
->options([
'wordWrap' => 'on',
'minimap' => ['enabled' => false],
'tabSize' => 2,
'scrollBeyondLastLine' => false,
'automaticLayout' => true,
]);
automaticLayout is the single highest-value option you can set — see gotchas.
Pair with a formatter action. A header action that pretty-prints JSON (or runs pint-style normalisation) via ->afterStateUpdated() is a small addition that pays for itself immediately, and gives non-technical users a safe "fix my mess" button.
Panel-level integration. If only some pages need the editor, gate the asset loading behind the resource rather than globally — Monaco is a multi-megabyte bundle and will dominate otherwise-light admin pages.
->height() explicitly, or rely on automaticLayout: true plus a parent with a real height. Avoid height: 100% inside a flex column that hasn't been sized.automaticLayout or a manual editor.layout() after the modal opens. If the editor appears collapsed in a modal, this is the cause, not the package.automaticLayout mitigates it; a ResizeObserver in custom JS is the belt-and-braces fix.worker-src 'self' blob: and the CDN origin in script-src/style-src if assets are remote. Air-gapped installs are the second failure mode — verify where the Monaco bundle is served from in the installed version and self-host if your environment forbids external origins.js should be javascript; there is no blade tokenizer — use html or php as the closest approximation. Validate the identifier against Monaco's language list, since an unknown id silently degrades to plaintext with no error.->theme('vs-dark') looks broken in a light panel. Either match Filament's mode explicitly or set a single theme and stop offering dark mode as a user choice in the admin.assertSet() on the state is the correct assertion; there is no meaningful way to unit-test cursor behaviour. Use fillForm(['source' => $json]) and assert persistence.^5.6) and read the compare diff before bumping minors — treat point releases as potentially behaviour-changing until proven otherwise.->view() to swap in your own Blade wrapper (the right hook for registering custom Monaco themes or extra language workers), the options array for editor behaviour, and a custom Alpine component if you need to talk to the monaco global directly. Everything else is intentionally out of scope for the package.How can I help you explore Laravel packages today?