DataGrid 0.8.0: the switch you turned off wasn't a wall
In the Livewire client, a public property set to false was a request, not a rule. 0.8.0 turns it into a rule — plus a PostgreSQL dialect fix and a 700-case database matrix.
Here is a small, ordinary piece of code. You have a grid of sales, and you decided nobody may export it:
class SalesGrid extends DataGrid
{
public bool $exportable = false;
}
You read it back. It says false. The Export button isn't there. You move on.
For every version of the Livewire client up to 0.7.x, that line was a request, not a rule. A user with a browser console could type one line, $wire.set('exportable', true), and the next call downloaded the table.
0.8.0 fixes that. It's the most important thing in the release, so I'd like to explain how it happened, because the reason is more interesting than the bug.
Why: every public property is a door
Livewire keeps a component's state in public properties, and the browser can write to any of them unless something refuses. That's how wire:model works, and it's what makes Livewire pleasant. It also means that a public property isn't only a piece of state. It's an input.
Most of a grid's public properties are supposed to be inputs. The search box, the page, the sort order, the page size, the column filters: all of those are the browser talking to the server, on purpose.
But $exportable isn't like those. Neither is $assistant, which turns on the natural-language filter and its billed LLM calls. Neither is the pivot layout, the id of a queued export, or the cached totals. These are settings that the host decides. They were public because Livewire needs them to be, and nothing told Livewire the browser must keep out.
The changelog is blunt about the consequences:
On a grid that relied on the route for authorization, one
$wire.set()re-enabled export, and the next call downloaded the table.The same call could switch on
$assistant, which meant billed LLM calls on a grid that never opted in.It could change the pivot layout, which bypassed
dimensions().It could tamper with the queued-export id and the cached totals.
I want to be careful about scope, because it's easy to overstate. This applies to the Livewire client. The server package and the Vue and Svelte clients don't depend on Livewire, and they're unchanged. And a grid that also checked authorization on the server side of the export wasn't handing out anything that check didn't already allow. The exposed case is the one the docs call withoutAuthorization(): a grid that trusts the route and the property to keep people out.
How: an allow-list, and why it isn't #[Locked]
The fix is an allow-list. The browser may now write only what the views actually bind: the search box, the page, sort, page size, the column filters, the filter builder, the ask box and the edit form. Anything else in the package's own configuration is refused.
The obvious tool would have been Livewire's #[Locked] attribute, and the changelog explains why it wasn't used. It's a good little piece of reasoning:
PHP does not inherit attributes onto a redeclared property, so a lock on
DataGrid::$exportablewould stop applying the moment a host wrotepublic bool $exportable = false;— which is exactly the case it exists for.
Read that twice, because it's the whole trap. The package can mark its own $exportable as locked, but your subclass redeclares the property to set it to false, and PHP doesn't carry the attribute along. The lock would vanish at precisely the moment you used the feature. So the guard is a Livewire trait hook instead. It runs whatever your subclass declares, and if you define your own updating(), that doesn't switch it off either.
The properties you add to your own subclass aren't touched. The guard only covers the package's.
How you set things now
Nothing about how you configure a grid changes. You set configuration on the server, as a subclass default or as a mount attribute, and both of those run on the server:
class SalesGrid extends DataGrid
{
public bool $exportable = false;
}
<livewire:sales-grid :assistant="true" />
What you can no longer do is have the browser do it for you.
What might break
The upgrade guide says three things can affect an existing app. I'll go through them in the order you're likely to hit them.
1. Your own tests. If a test flips a setting the way a browser would:
Livewire::test(SalesGrid::class)->set('assistant', true);
it now throws CannotUpdateLockedPropertyException, because ->set() is exactly what the browser does. That's the guard working. Pass the configuration at mount instead:
Livewire::test(SalesGrid::class, ['assistant' => true]);
If you have a custom view that binds one of the package's own properties with wire:model, open it explicitly:
protected function clientWritableProperties(): array
{
return [...parent::clientWritableProperties(), 'groupDetailRows'];
}
2. The floor moved. The Livewire client now requires Livewire 4, Flux 2.6.1 and Laravel 10.48. The last two numbers aren't arbitrary. Livewire 4.0 declares Laravel ^10.0 but calls a Blade API that was added in 10.16, and Flux 2.6.1 uses one from 10.40. The lowest-dependencies run in CI is what found the real Laravel floor.
There's an honest admission tucked into that entry: the old ^3.5 || ^4.0 promise was never tested on Livewire 3. If you were on Livewire 3, you were relying on a claim nobody had checked. Now the requirement says what's true.
3. The dialect follows the connection. This one is a bug fix that changes a default, so it deserves a slow read.
Before 0.8.0, a grid without ->driver() got the ElyraSQL dialect, whatever database it was actually connected to. On MySQL that happens to be fine. On PostgreSQL it isn't: the SQL came out backtick-quoted, and the first query failed with syntax error at or near "AS". The changelog says it was reproduced against a live server.
The dialect now follows the connection:
Connection Dialect pgsql PostgreSQL sqlite SQLite clickhouse ClickHouse mysql / mariadb ElyraSQL (they can't be told apart)
An explicit ->driver() still wins. The upgrade guide flags one consequence: a SQLite grid that had been silently getting ElyraSQL's dialect, and relied on its aggregates or search, should say so:
->driver('elyrasql')
The test that couldn't have found it
The part of this release I like best is the story of why the PostgreSQL bug survived.
DataGrid has a live PostgreSQL suite. It passed. It passed for a long time, because every grid in that suite called ->driver('pgsql'). The one situation that failed was the one nobody wrote a test for: a grid that just works out its database. The suite was testing that PostgreSQL works when you tell it it's PostgreSQL.
There is now a grid in that suite that doesn't say. The lesson is a small one and I keep relearning it: a test that always supplies the answer isn't testing the question.
Four databases, seven hundred cases
That's where the other headline of 0.8.0 comes from. Every case in the request matrix now runs on SQLite, PostgreSQL and MySQL 8.4: 700 cases, up from 466. Among them is a check that fulltext search actually finds rows, rather than merely not erroring.
That check found something the docs hadn't said. MySQL's fulltext search needs a FULLTEXT index over exactly the columns you search, and without one it fails with error 1191. The docs now say so. (I'd like to think most of us learn about error 1191 once, at two in the morning. It's nicer to read it in a guide.)
CI also got a framework matrix: a job that resolves fresh against Laravel 10, 11 and 13, plus a lowest-dependencies run. Laravel 10 and 11 are end-of-life and every release carries a security advisory, so Composer refuses them by default. The job lifts that for those two rows only, to prove the code runs, not to endorse running them.
Should you upgrade?
If you use the Livewire client: yes, and soon. Any grid with an export button switched off, or an assistant you didn't opt in to, was one console line from doing the opposite.
Here's the short version of how:
Bump to 0.8.0 and read the upgrade guide's three items.
Run your tests. If one that calls
->set('assistant', true)now throws, pass the value at mount.Check your Livewire, Flux and Laravel versions against the new floor: 4, 2.6.1 and 10.48.
If any grid on SQLite leaned on ElyraSQL's aggregates or search without saying
->driver(), add->driver('elyrasql').
And if you're not on Livewire at all (Vue, Svelte, or the plain server package), this release changes nothing for you except the MySQL test coverage, which is the kind of thing you'd rather have and never think about.
The point
I think the honest way to describe 0.8.0 is that it's a release about saying what's true. The property that said false now means it. The compatibility promise that was never tested now names the versions that were. The PostgreSQL support the page advertised now works without a hint. Even the MySQL error that used to bite people at two in the morning is in the docs.
None of it is glamorous, and the code for the fix is small. But a switch that only works while the user is well-behaved isn't a switch. And now, in the Livewire client, it is.