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 Monaco Editor Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. The README usage block — the public API surface is small (language(), theme(), height(), plus an options passthrough), so a five-minute read covers 90% of it.
  2. The field class itself under 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.
  3. The Blade view — it shows which Monaco constructor options are wired up versus which you must pass manually.

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.

Implementation Patterns

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.

Gotchas and Tips

  • Zero-height editors. The most common "it renders but I see nothing" is a container with no height. Set ->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.
  • Modals and hidden tabs. Monaco cannot measure a hidden element. On Filament modal forms and tabbed layouts you need 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.
  • Sizing after Livewire re-render. Full-page Livewire navigation can leave the editor at its pre-navigation dimensions. automaticLayout mitigates it; a ResizeObserver in custom JS is the belt-and-braces fix.
  • CSP. Monaco spawns Web Workers. Under a strict Content-Security-Policy you typically need 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.
  • Language identifiers are Monaco's, not yours. 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.
  • Dark mode mismatch. A hardcoded ->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.
  • Don't put secrets in a browser-side editor. Anything in the field is in the DOM and in Livewire's payload. Server-only values belong in a masked read-only field or nowhere near Monaco.
  • Testing. Livewire tests don't execute JavaScript, so 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.
  • Version pinning. The 5.0.0 → 5.6.3 span implies rapid iteration with sparse public notes. Pin a caret constraint (^5.6) and read the compare diff before bumping minors — treat point releases as potentially behaviour-changing until proven otherwise.
  • Extension points worth knowing: ->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.
  • Escape hatch. If you need diff editors, multi-cursor coordination, or LSP integration, this field will not get you there — reach for a purpose-built JS component instead of fighting the wrapper.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource