Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Elyra

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 throws CannotUpdateLockedPropertyException, 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 without type: 'number' still accepts contains — 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.php raised a Blade parse error in 0.4.0 and 0.5.0 — Livewire 4 injects PHP into the opening tag of any looped element carrying wire: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. Schedule elyra-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, more limits). 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/update previously returned the row via SELECT *, 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.
  • .npmrc is 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().