Upgrading
Packages are versioned together, so a single tag moves the server package, the three clients and the protocol at once. Upgrade them as a set.
composer update elyra/datagrid-server elyra/datagrid-livewire elyra/datagrid-js elyra/datagrid-protocol
That is the whole upgrade. There is nothing to npm update: the JavaScript
clients ship inside elyra/datagrid-js, and the @elyra/* specifiers in your
Vite config are aliases onto vendor/elyra/datagrid-js/dist/ — so updating the
Composer package updates the clients. See
Installation.
0.8.x → 0.9.0
Laravel 13.30 and PHP 8.3 are now the minimum. Nothing else changes, but this is the whole release for anyone below it: on Laravel 10, 11 or 12, or on PHP 8.2, stay on 0.8.x until you have upgraded the framework.
| 0.8.x | 0.9.0 | |
|---|---|---|
| PHP | 8.2+ | 8.3+ |
| Laravel | 10.48+, 11, 12, 13 | 13.30+ |
| Livewire (Livewire client) | 4.0+ | 4.3.4+ |
| Flux (Livewire client) | 2.6.1+ | 2.12.2+ |
None of the new floors is arbitrary — each is the lowest version with no known security advisory, or the first that supports Laravel 13:
- Laravel 13.30.0 is the first 13.x not affected by CVE-2026-102279 (XSS in the debug page), which covers every release below 12.69.0 and 13.0–13.29.
- PHP 8.3 because Laravel 13 requires it.
- Livewire 4.3.4 is the first 4.x not affected by CVE-2026-81887 (DOM-based XSS during client-side state handling), which covers 4.0.0 through 4.3.3.
- Flux 2.12.2 is the first release that allows Laravel 13.
Composer blocks packages with known advisories by default, so the Laravel and Livewire floors are also simply what it would resolve to anyway.
composer require "laravel/framework:^13.30" -W # if you are not there yet
composer update elyra/datagrid-server elyra/datagrid-livewire elyra/datagrid-js elyra/datagrid-protocol
0.7.x → 0.8.0
Two security-relevant fixes and a raised floor. Check each against your app.
1. The Livewire client requires Livewire 4
livewire/livewire is now ^4.0 (was ^3.5 || ^4.0), livewire/flux is
^2.6.1 (the first Flux release that supports Livewire 4), and Laravel must be
10.48 or later. Livewire 4.0 declares Laravel ^10.0 but calls Blade APIs that
arrived in 10.16, and Flux uses one from 10.40 — so the real floor was never
10.0. Laravel 11, 12 and 13 are unaffected.
The server package and the Vue/Svelte clients do not depend on Livewire and are unchanged.
2. The browser can no longer change a grid's configuration
Setting public bool $exportable = false; now holds even against a user who
calls $wire.set('exportable', true). Before, that one call re-enabled export on
a grid that relied on the route for authorization. The same goes for every
feature switch, cap and piece of internal state the package declares.
Configuring a grid the normal ways is unaffected — a subclass default, or a mount
attribute (<livewire:sales :assistant="true" />). What changes:
-
Your own tests.
Livewire::test(SalesGrid::class)->set('assistant', true)now throwsCannotUpdateLockedPropertyException, because->set()is what the browser does. Pass configuration at mount instead:Livewire::test(SalesGrid::class, ['assistant' => true]); -
Custom views that bind a package property with
wire:model. Open it explicitly — see Livewire client.
Properties you added to your own subclass are not affected.
3. Without ->driver(), the dialect follows the connection
| Your connection | Before | Now |
|---|---|---|
pgsql |
ElyraSQL dialect — failed on the first query | PostgreSQL |
sqlite |
ElyraSQL dialect | SQLite |
clickhouse |
ElyraSQL dialect | ClickHouse |
mysql, mariadb, other |
ElyraSQL | ElyraSQL (unchanged) |
An explicit ->driver() always wins, so a grid that already names its driver
does not change. The one behaviour change worth a look is SQLite without
->driver(): it now gets the SQLite dialect's aggregates and search instead of
ElyraSQL's. That is the correct dialect, but if you were relying on something
that only happened to work, set ->driver('elyrasql') to keep the old behaviour.
0.5.x → 0.6.0
Mostly additive — pivot mode, queued exports, live updates — but four defaults tightened and one dead surface was removed. Work through these.
1. A LIKE-style filter on a non-string column is refused
contains, ncontains, startsWith and endsWith now require a column declared
as string. A boolean accepts only eq, neq, in, nin and the null tests.
This used to appear to work: MySQL and SQLite coerce silently, PostgreSQL raises
operator does not exist: numeric ~~ text and the request 500s. Rather than behave
differently per dialect it is refused everywhere, with a GridException.
// If a grid genuinely filters a numeric column with `contains`, declare it as text
->column('invoice_no', type: 'string') // rather than 'number'
// …or switch the filter to eq / between.
Affects you if a grid filters a number, date or bool column with a
LIKE-style operator, or a bool column with an ordering operator. The clients'
filter menus never offered these, so a hand-built request is the likely source.
Declare types truthfully. The default is
string, so an integer key declared withouttype: 'number'still acceptscontains— and then fails on PostgreSQL. The package cannot detect a mis-declared type.
2. Filter values are checked against the column type
A value that can be coerced is ("20" on a number column becomes 20); one that
cannot — "abc" on a number, "maybe" on a bool, an unparseable date — is now
refused with a GridException instead of reaching the database. This is also how
the natural-language assistant could produce a 500.
Affects you if anything sends deliberately loose values and relied on MySQL's coercion.
3. view.take: 0 returns no rows
It used to be clamped up to 1, so an aggregates-only request always dragged a
full-width single-row SELECT along with it. Pass take: 1 if you wanted a row.
Affects you if you build requests by hand with take: 0 and expected one row.
4. The LIKE escape character is !, not \
likeEscapeClause() returns ESCAPE '!' and escapeLike() escapes with !. The
SQLite and PostgreSQL overrides are gone.
Not cosmetic: a literal backslash in the SQL breaks PDO's own placeholder scanner
on PHP 8.2 and 8.3, which raises Invalid parameter number for any placeholder
after it — two contains filters in one query was enough.
Affects you if you implement a custom GridDriver or assert on generated SQL.
ClickHouse still escapes with a backslash, because it has no ESCAPE clause.
5. rollup is gone
GroupSpec.rollup, GroupRow.isGrandTotal, supportsRollup() and the $rollup
parameter of groupBy() are removed. The flag was documented and plumbed but never
read by the engine — groupBy() was always called with false.
Subtotals are unaffected and were never produced by ROLLUP: the engine runs one
GROUP BY per level, which is why avg/median subtotals are correct.
Affects you if you send rollup: true (harmless to drop) or implement a custom
driver (remove the two members).
Also worth knowing
- The Livewire grid renders again on Livewire 4.
data-grid.blade.phpraised a Blade parse error in 0.4.0 and 0.5.0 — Livewire 4 injects PHP into the opening tag of any looped element carryingwire:key, which Blade's component-tag compiler cannot parse. No action needed; if you are on Livewire 4 this is the reason to upgrade. - Queued exports need a queue worker and, for the download link,
Grid::exportRoutes()mounted under your own middleware. Scheduleelyra-datagrid:prune-exports— nothing else deletes the files. - Live updates need
->versionColumn('updated_at')to see in-place edits, and that column indexed alongside whatever the grid filters on. - Several new keys in
config/datagrid.php(export, morelimits). Laravel merges package defaults, so an unpublished config keeps working; re-publish or merge by hand if you have published it.
0.4.x → 0.5.0
A security and hardening release. Most of it is invisible, but four changes tighten defaults and can reject requests that used to succeed. Work through the checklist below; each item tells you how to tell whether it affects you.
1. The natural-language assistant is off until you enable it
ELYRA_DATAGRID_ASSISTANT was documented as the way to switch the assistant on,
but nothing ever read it. A component with public bool $assistant = true;
therefore worked with no env var set — and, less comfortably, ask() was
reachable even on grids that had left the assistant off, since only the UI was
hidden.
The flag is now enforced server-side. If you use the assistant:
ELYRA_DATAGRID_ASSISTANT=true
Questions are now also bounded, because each one is a billed LLM call:
| Setting | Default |
|---|---|
datagrid.assistant.max_query_length |
500 characters |
datagrid.assistant.rate_limit.attempts |
10 |
datagrid.assistant.rate_limit.per_seconds |
60 |
Callers are metered per authenticated user (falling back to IP) and per grid.
Set attempts to 0 to disable metering. The limiter fails closed: if its cache
store is unavailable, translation stops rather than running unmetered.
Affects you if any grid sets $assistant = true or calls
GridDefinition::ask().
2. Exporting a keyless grid needs an explicit cap
A grid with no ->key(...) cannot be paginated — there is no stable order to page
by — so its export read the entire result set into PHP memory. With
max_export_rows defaulting to null that was an OOM waiting to happen, so
export() now refuses rather than risking it.
Pick one:
// Preferred: give the grid a key, and the export streams in bounded batches.
GridDefinition::table('sales')->key('id')->columns([...]);
// Or accept a bounded export. config/datagrid.php:
'limits' => ['max_export_rows' => 100_000],
// or ELYRA_DATAGRID_MAX_EXPORT_ROWS=100000
Affects you if a grid that omits ->key(...) is exportable.
3. Facets and grouping now respect filterable: false
Both emit a column's distinct values, which is exactly what the flag withholds —
but neither checked it, so a column marked unfilterable could still have every
value in it enumerated through facets. Both now throw a GridException.
If a grid faceted or grouped such a column on purpose, declare it filterable:
->column('region', filterable: true)
Facet lists are also bounded now: a request that omits top gets
GridLimits::maxFacetValues (1000) instead of every distinct value.
Affects you if a grid facets or groups a column declared
filterable: false.
4. Requests are bounded by breadth, not just depth
maxFilterDepth capped nesting, but nothing capped width — a shallow request
with thousands of conditions, or dozens of group levels (one query each), was a
DoS lever on any grid with anonymous read access.
| Limit | Default |
|---|---|
max_filter_conditions |
200 |
max_group_levels |
5 |
max_aggregates |
25 |
max_facets |
25 |
max_search_length |
200 |
All live under datagrid.limits and are overridable per grid via
GridDefinition::limits().
Affects you if a grid nests more than five group levels, or builds filters
with more than 200 conditions. max_group_levels is the one most likely to bite.
Also worth knowing
- Mutation responses now carry only the grid's declared columns.
create/updatepreviously returned the row viaSELECT *, handing the client every column in the table — password hashes, tokens, internal flags. If a client read something else out of a mutation response, declare that column. - Grouping now works on PostgreSQL. It raised a syntax error on every PostgreSQL install because the grouping query hardcoded MySQL-style backtick quoting. No action needed; grouping simply starts working.
- Pinned rows no longer break copy and paste. Row order had two definitions — one for rendering, one for index-based operations — so with a row pinned, a paste wrote to the wrong records. No action needed.
.npmrcis now gitignored. If you keep a registry token in a project-local.npmrc, confirm it is not already committed:git log --all --oneline -- .npmrc.
Republishing config
Several new keys were added to config/datagrid.php. Laravel merges package
defaults, so an unpublished config keeps working. If you have published it,
re-publish or merge by hand:
php artisan vendor:publish --tag=elyra-datagrid-config --force
Earlier versions
See the changelog. The one earlier upgrade note worth
repeating: 0.4.0 made writes and exports secure-by-default, so mutate(),
mutateBatch() and export() throw unless the grid declares either
->authorize(...) or ->withoutAuthorization().