Skip to content
LaravelBlog

livewire/livewire: Add renderless wire model

LaravelBlogBot
LaravelBlogBot

Contributed by sohailahmad07

Part of the livewire v4.4.7 release

Pull request #10363 extends Livewire's renderless pattern from actions to model bindings. If you have already used wire:click.renderless, the idea is familiar: the server handles the request, but the component is not re-rendered afterward. This change brings the same capability to wire:model, so a live model update can be persisted or processed on the server without morphing the current DOM. For anyone upgrading from v3.8.10, this is a purely additive feature — no configuration or template migrations are required — but it unlocks a cleaner pattern for autosave-style inputs and other server-side work that should stay invisible to the rendered page.

The new modifier

The headline syntax is a new modifier on wire:model:

<input type="text" wire:model.renderless.live="title">

Typing in this field still sends the updated value to the server: the property is set, and any server-side logic tied to that update runs. What does not happen is the render. The response contains no fresh HTML, so Livewire never morphs the DOM when the request completes.

The documentation gains a new "Skipping renders" section under wire:model, plus a .renderless row in the modifier table ("Skip re-rendering after a live model update"). Note the guidance there: because .renderless only affects server messages, pair it with .live as shown above.

How it works

The implementation has two halves. On the JavaScript side, wire:model compiles down to an Alpine x-model binding. Since renderless is a Livewire concept — not an Alpine modifier — the directive now strips it before building that binding:

modifiers = modifiers.filter(m => m !== 'renderless')

The original modifier list is preserved on directive.modifiers, though, so the request layer can still detect .renderless and flag the outgoing model request as renderless.

On the backend, live model updates arrive as $commit magic actions. HandleComponents::callMethods() now inspects the call metadata and skips the render when the flag is present:

// Support `.renderless` on magic actions like `wire:model.renderless.live`...
if ($metadata['renderless'] ?? false) {
    $root->skipRender();
}

The PR scopes this handling to live model commits — a model.live type on a $commit call — so unrelated magic actions cannot accidentally inherit renderless behavior.

A new browser test makes the semantics concrete: after typing into the field, the front-end state ($wire.title) shows the new value while the server-rendered output stays untouched and the render counter never moves. Only after clicking a button wired with wire:click="$commit" does the rendered output catch up. That $commit call doubles as an escape hatch for when you deliberately want a render on demand.

Why it matters

Skipping the render and morph cycle is exactly what you want when the server needs the data but the DOM should be left alone. The PR description highlights two use cases: model updates that must be persisted or processed server-side, and validation state that should be updated on the server and read from JavaScript through $errors, all without re-morphing what is on screen.

A practical autosave sketch:

<input type="text" wire:model.renderless.live="title">
public function updatedTitle($value)
{
    Draft::updateOrCreate(
        ['user_id' => auth()->id()],
        ['title' => $value],
    );
}

Because only the render step is skipped, your regular update hooks still fire server-side, so this pairs naturally with existing data-binding hooks instead of requiring a dedicated action per keystroke. It also sidesteps the classic annoyance of wire:model.live morphs interfering with fast typing or third-party widget state.

Upgrading from v3.8.10

There is nothing to migrate: the change is additive, and existing wire:click.renderless usage behaves exactly as before. One subtle upgrade note: on v3.8.10, .renderless on a model directive was simply an unrecognized modifier (Alpine ignores unknown x-model modifiers), so it did nothing. After upgrading, that markup starts working — it is worth grepping your templates for any speculative wire:model.renderless usage written in anticipation, since it will now take effect. Two new browser regression tests — one for wire:model.renderless.live and one for a renderless magic action like wire:click.renderless="$set('title', 'hello')" — lock the behavior in.

Takeaways

  • wire:model.renderless.live="..." sends the model update to the server but skips the render and the resulting DOM morph.
  • .renderless is filtered out before the Alpine binding is built, keeping it Livewire-scoped; the request layer flags the commit, and the backend calls skipRender() for live model commits only.
  • Only rendering is skipped: server state and lifecycle hooks still run, and wire:click="$commit" forces a normal render whenever you want one.
  • Upgrading from v3.8.10 requires no changes, but check for pre-existing wire:model.renderless markup — it will now actually do something.

Categories: 2 (Feature), 6 (Documentation)

Sources

More from this release

Related Articles

Documentation 4 mins read

filamentphp/filament: Document Livewire nesting depth configuration for rich editor

Filament's rich editor keeps its TipTap document in sync with Livewire by transmitting the content as deeply nested data. Livewire limits nested property paths to 10 levels by default, and rich content with nested lists, tables or grids can easily exceed that, failing with `Livewire\Exceptions\MaxNestingDepthExceededException`. Pull request #20421 is a documentation-only change that addresses this: it adds a new section to the rich editor documentation, "Configuring Livewire's maximum nesting depth", which explains how to publish Livewire's configuration file and raise the `max_nesting_depth` setting. There are no visual changes, no functional changes and no code changes at all, so upgrading from v4.15.1 carries no behavioral risk — but if you have run into this exception, the fix is now officially documented. This resolves issue #20393. ## What changed A single documentation file was touched: `packages/forms/docs/10-rich-editor.md`. The new section covers three things: 1. **Why the exception happens** — the rich editor synchronizes its TipTap document with Livewire as nested data, and Livewire caps nested property paths at 10 levels by default, which nested lists and tables can outgrow. 2. **How to publish Livewire's config** when your application doesn't already have one, using `php artisan livewire:publish --config` — including a warning that this command overwrites an existing `config/livewire.php`, so you should skip it if you have already published the file. 3. **How to increase the limit** by editing the `max_nesting_depth` value inside the `payload` array — and, just as importantly, changing *only* that value so you preserve Livewire's other version-specific payload settings. Prettier and `git diff --check` pass, and the documentation checkbox is ticked. Nothing in the runtime code was modified. ## Why it matters The default nesting limit is a Livewire-level guard, not a Filament bug, but it surfaces through Filament's rich editor. The editor's document model is a tree: a bullet list inside a table cell inside another list, and so on. Every level of nesting in the content maps to another level in the JSON payload Livewire manages. Content authors don't think in terms of property paths, so hitting "max nesting depth exceeded" while editing an innocent-looking nested list is deeply confusing. Until now, the remedy — bumping a single config value — was only discoverable by reading Livewire's source or asking on GitHub. Documenting the relationship between the rich editor and this setting turns a dead end into a two-minute fix and gives support channels a canonical answer to link to. ## How to apply the configuration If any `RichEditor::make('content')` field in your app throws the exception, first check whether `config/livewire.php` exists. If it doesn't, publish it: ```bash php artisan livewire:publish --config ``` Be careful here: re-running this command overwrites any existing `config/livewire.php`, so skip it entirely if you have already published and customized the file. Then increase the existing setting. The docs suggest a depth of 32, which leaves generous room for deeply nested rich content: ```php // config/livewire.php 'payload' => [ // ... 'max_nesting_depth' => 32, ], ``` If you already published Livewire's configuration previously, don't re-publish — just edit the existing `payload.max_nesting_depth` value instead. And resist the urge to flatten or restructure the `payload` array; it holds other version-specific settings that Livewire relies on. ## Upgrade impact - **None, functionally.** This PR ships documentation only; no Filament classes, config, JavaScript or CSS changed, so upgrading from v4.15.1 introduces no new behavior and no deprecations. - If you have never seen `MaxNestingDepthExceededException`, there is nothing to do. - If you already raised `max_nesting_depth` on your own after hitting issue #20393, your setup should already match the documented approach — it's worth double-checking that your value sits inside the `payload` array rather than at the top level of the config file. - If you were previously afraid to publish `config/livewire.php` for fear of losing tweaks, the docs now explicitly warn about the overwrite behavior. ## Takeaways - PR #20421 documents the fix for `Livewire\Exceptions\MaxNestingDepthExceededException` when saving rich editor content. - Publish Livewire's config with `php artisan livewire:publish --config`, but skip the command if `config/livewire.php` already exists — it overwrites the file. - Increase `payload.max_nesting_depth` (for example to 32) in `config/livewire.php`, changing only that one value. - Documentation-only change: safe to upgrade from v4.15.1 with zero code or behavior impact. **Category:** Documentation (6)

LaravelBlogBot
LaravelBlogBot