Secondary filters, a single-column layout for small screens, Laravel 11 support, a round of access-control and stability fixes, and a test suite. No database change.
Upgrade notes
- Run
php artisan filament:assetsandphp artisan view:clearafter updating. - Laravel 11.28 is now the minimum version.
- In a panel without SPA mode, the page now does a regular page load after a widget is added, edited or deleted or the dashboard is switched, and the canvas assets are only loaded on dashboard pages. See Changed.
- If you published the package's views, update your copies of
livewire/dashboard-grid.blade.phpandschemas/widget-wrapper.blade.phpfrom the package. Without the new@assetsblock of the first one, the canvas scripts are no longer loaded outside SPA mode. currentDashboardIdcan no longer be set from the browser. Custom JavaScript that wrote it through$wirehas to trigger the dashboard selector instead.- The
available()/availableFor()model scopes are now the public methodsscopeAvailable()/scopeAvailableFor(). Calls are unchanged, but a subclass that overrode the former protected methods must rename its override.
Added
- Secondary filters. Override the new
DynamicDashboard::getSecondaryDashboardFilters()to keep the main filters on the bar and put the others behind a More filters toggle, as a flat list of filter names or grouped under headings. The toggle shows how many secondary filters have a value, the area opens instantly (no request), keeps its state across re-renders, and stays open while one of its filters has a value. Secondary filters remain ordinary filters for default values, per-dashboard visibility, the session, reset andpageFilters. A page that does not override the method renders exactly as before. New translation keysmore_filtersandmore_filters_countin every locale. - Small-screen layout. Below 768px, whatever the template, sections stack and every widget takes one full-width row, in reading order. Each widget is at least as tall as it was designed and grows with its content. Widgets cannot be dragged or resized in that layout and nothing is saved, so the grid layout is never altered from a phone; widening the window brings the grid back without a reload.
- Locale aliases
pt_BRandzh_CN, which reuse theptandzhtranslations. Applications set to those locales previously fell back to English. Dashboard::visibleToCurrentUser()query scope (global dashboards plus the current user's personal ones).DashboardWidget::nextFreeRow()— first free row of a dashboard section.DynamicDashboard::redirectToDashboard()— the reload performed after a change the canvas cannot apply in place; override it to choose between a Livewire navigation and a regular redirect.CanvasJs/CanvasCssasset classes, which decide per panel whether the canvas assets are loaded on request.- GridStack's licence files next to the vendored bundle in
resources/dist. - Test suite (Pest), PHPStan/Larastan (level 5), a Pint configuration, a GitHub Actions workflow and
.gitattributes.
Changed
- Minimum Laravel version is now 11.28 (the lowest version Filament 4 supports);
illuminate/contractsconstraint updated accordingly. - After a widget is added, edited or deleted, or the dashboard is switched, the page reloads the way the panel is configured, like Filament's own pages: a Livewire navigation in SPA mode, a regular page load otherwise (it was always a Livewire navigation).
- In a panel without SPA mode, GridStack and the canvas CSS/JS are only added to dashboard pages instead of every panel page. Nothing changes in SPA mode.
- The page reads the current dashboard through
getCurrentDashboard()everywhere, instead of relying on it having been loaded earlier in the request. - The
TemplateRegistrysingleton is registered inpackageRegistered(). spatie/laravel-permissionis listed undersuggestincomposer.json.- On devices without hover (touch screens) the widget icons (drag, edit, delete) are always visible instead of appearing on hover.
- The page subheading falls back to the page's own subheading when the dashboard has no description, instead of rendering an empty element.
- README: upgrade notes for 1.2, the ApexCharts sizing advice (
chart.height: 'auto'), and corrections where it no longer matched the code (number of shipped presets, default widget size, what a section'snameis used for, what stays visible on a locked dashboard).
Deprecated
DashboardWidget::getWidget()— no longer used by the package; it will be removed in the next major version.
Removed
- The unused
templateColumnsoption of thedashboardGridAlpine component.
Fixed
- Laravel 11 compatibility: the
available()/availableFor()scopes used the#[Scope]attribute, which only exists from Laravel 12. They are now classicscopeAvailable()/scopeAvailableFor()methods — call sites are unchanged. - After a dashboard was unlocked live from the manager, drags were not saved until the page was reloaded. On a dashboard loaded unlocked, the dragged widget never finished its move: the canvas kept its GridStack instances in Alpine's reactive data, which hands them back wrapped in proxies; they are now always read from the elements GridStack attaches them to. On a dashboard loaded locked, the handlers that save a move were never attached; they are now attached whatever the state at load.
- A stored widget type naming a class that is not a dynamic widget any more no longer breaks the page; the widget is skipped, like one whose class is gone.
- Widgets are keyed by their id (
key('widget-'.$id)); the key was passed in a position the@livewiredirective ignores. Livewire derived a key from the widget's position instead, so after a widget was dragged to another section the next page re-render mounted it a second time on the server. - The canvas component was initialised twice (
x-init="init()"on top of Alpine's owninit()call), registering every listener twice. - The
dynamic-dashboard:editable-changedlistener, the per-widget Livewirecommithook and the GridStack instances are now released when the canvas leaves the page. They used to pile up on the window with everywire:navigatevisit. - New widgets, and widgets moved by a template change, are placed on the first row below the lowest edge (
y + h) of the widgets already in the section. They were placed atmax(y) + 1, which overlapped any widget taller than one row. - A dashboard's
templateattribute was cached on the model instance, so a secondtemplate_keychange on the same instance still resolved the previous template and skipped the move of orphan widgets. persistLayout()skips items with non-numeric coordinates, a negative position or an empty size, and stores a section slug unknown to the current template as the template's first section (the fallback already applied at render time) instead of saving the stale slug.AsWidgetSettingsno longer passesnulltojson_decode()when a widget'ssettingscolumn isNULL(PHP deprecation), always returns an array, no longer instantiates the widget class to read its casts, and no longer fails when the model was loaded without itstypecolumn.- Soft-deleting a user no longer hard-deletes their personal dashboards; they are removed when the user is force-deleted. The listener is registered on the event dispatcher, so the user model is no longer booted on every request.
upgrade_dynamic_dashboard_tables_to_v2migration: foreign keys and indexes are now checked withSchema::getForeignKeys()/Schema::hasIndex()instead oftry/catchblocks that either never caught anything (inside Blueprint closures) or left a failed statement behind (which aborts the migration transaction on PostgreSQL). Only affects installs that have not published the migration yet.- The dashboard manager counted the available dashboards up to four times per table row; the count is now computed once per request.
- Translations: the 21 locales other than
en/frwere missingtemplate,section,drag,personalandpersonal_help, and still carried the removed v1 grid/block keys. All 23 locales now share the same keys.
Security
- Widget edit and delete actions now resolve the widget inside the current dashboard only. A forged widget ID could previously reach any dashboard, including another user's personal one.
- Adding, editing and deleting a widget are refused on the server while the dashboard is locked, not only hidden in the page. Saving the layout (
persistLayout()) now follows the same rule as those actions and is also refused once the dashboard is no longer available to the user (deactivated, or turned into someone else's personal dashboard). - Without an authenticated user, personal dashboards are never visible. A personal dashboard whose creator no longer exists (
created_bynull) used to match "no user". - The dashboard manager table no longer lists other users' personal dashboards, so they can't be edited, toggled, reordered or deleted from it (this is the behaviour the README already described).
currentDashboardIdon the page, andpageClass/currentDashboardIdon the manager, are now#[Locked]. A client-side update could previously switch to a dashboard without going throughcanDisplay().- The dashboard description is sanitized before being rendered as the page subheading.