anselmocossa / filament-launchpad
Transform your Filament v5 panel homepage into a SAP Fiori-style launchpad: Spaces, Pages, Sections and drag-and-drop KPI/shortcut/widget cards with role-based visibility.
Package info
github.com/anselmocossa/filament-launchpad
pkg:composer/anselmocossa/filament-launchpad
Requires
- php: ^8.2
- filament/filament: ^4.0|^5.0
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- laravel/prompts: ^0.1.24|^0.2|^0.3
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.0
- nunomaduro/collision: ^7.0|^8.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
- spatie/laravel-permission: ^8.3
Suggests
- bezhansalleh/filament-shield: Required to manage the Permission field's roles from a Filament UI (Spatie roles/permissions admin).
- spatie/laravel-permission: Required for role-based visibility on Spaces/Pages/Sections/Cards; without it, everything is visible to everyone.
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v1.16.0
- v1.15.0
- v1.14.0
- v1.13.0
- v1.12.1
- v1.12.0
- v1.11.1
- v1.11.0
- v1.10.6
- v1.10.5
- v1.10.4
- v1.10.3
- v1.10.2
- v1.10.1
- v1.10.0
- v1.9.1
- v1.9.0
- v1.8.2
- v1.8.1
- v1.8.0
- v1.7.1
- v1.7.0
- v1.6.6
- v1.6.5
- v1.6.4
- v1.6.3
- v1.6.2
- v1.6.1
- v1.6.0
- v1.5.2
- v1.5.1
- v1.5.0
- v1.4.0
- v1.3.1
- v1.3.0
- v1.2.0
- v1.1.13
- v1.1.12
- v1.1.11
- v1.1.10
- v1.1.9
- v1.1.8
- v1.1.7
- v1.1.6
- v1.1.5
- v1.1.4
- v1.1.3
- v1.1.2
- v1.1.1
- v1.1.0
- v1.0.0
This package is auto-updated.
Last update: 2026-10-20 10:30:30 UTC
README
Turn your Filament panel's homepage into a SAP Fiori-style launchpad: Spaces, Pages, Sections and drag-and-drop KPI/shortcut/widget cards, with role-based visibility and a full builder UI — all rendered inside the native Filament shell.
Screenshots
The launchpad home page in light mode: sub-nav tabs, sections and tile cards.
The same home page in dark mode — panel chrome (topbar, sidebar) stays native.
->topbarNavigation(): the spaces sit beside the panel logo, with no bar below the topbar. KPI tiles draw a chart of their last twelve months.
A space with several pages opens a dropdown of its pages.
Spaces that don't fit beside the logo move to a "More" dropdown («Mais» in Portuguese).
A resource opened from a card keeps its space active: Products, opened from Shop › Catalog, still shows Shop.
Below 768 px the list becomes a single ☰ with every space and its pages.
KPI tiles with a sparkline: an area in the default accent color, bars, or an area in a color of your choice.
The layout builder: drag cards from the library or existing sections to rearrange the page.
"Edit Home" in the account/user menu — a one-click shortcut into the builder for the home page.
Edit Home: each user arranges their own home — adds available cards and drags them into place; cards fixed by the administrators show a lock. The breadcrumb leads back to the launchpad.
The "Permission" field on a Space/Page/Section/Card, restricting visibility to selected roles.
Managing Spaces, Pages and Sections through the plugin's Resources.
The flat Cards index, also reachable from Filament's global search.
A native Filament widget (StatsOverviewWidget/ChartWidget) rendered in place of a card.
Features
- Fiori-style launchpad home page — sub-nav tabs, grouped sections, and a grid of clickable cards, rendered inside the native Filament page shell (topbar, sidebar, breadcrumbs and dark mode stay untouched).
- Space → Page → Section → Card hierarchy — manage everything through dedicated Filament Resources and nested relation managers.
- Sub-nav navigation — "☰ All Spaces" menu, a per-space Pages dropdown, and an automatic "More ▾" overflow (priority-nav) so the tab bar never scrolls.
- Topbar navigation — optionally render spaces and pages beside the panel logo; the list collapses into a "More" dropdown and, on small screens, a single ☰, and it keeps the space you came from while you work in a page opened from one of its cards.
- Three card types — KPI (live value from a
KpiSourceclass), Shortcut (link to a Resource, Page or URL), and Widget (renders a nativeStatsOverviewWidget/ChartWidgetin place of the card). - Class-based KPI engine — a
KpiSourcereturns a richKpiResult(value + unit + trend + badge, and optionally an area or bar sparkline) from one query; auto-discovered underapp/Filament/Launchpad, resolved lazily with per-request memoization and an optional (tenant-safe) cached TTL, and scopeable per panel. Legacy closure sources still supported; a throwing source degrades the tile to—. Nevereval'd, never user-controlled. - Artisan generators —
make:launchpad-kpi,make:launchpad-widgetandmake:launchpad-cardscaffold KPI/widget/card-preset classes (autoKpi/Widget/Cardsuffix, optional--model=subfolder). - Reusable card library — draggable presets for the builder, defined once and dropped into as many sections as needed.
- Auto-discovered widget library — widgets already registered on the panel are available in the builder out of the box; override label/icon/column span or add extra ones only when needed.
- Drag-and-drop layout builder — native HTML5 Drag and Drop (Alpine-driven, no external JS library), with a searchable Card Library and Widgets panel.
- "Edit Home" shortcut — a one-click entry in the account/user menu straight into the builder for the home page, without navigating the Spaces/Pages tree; the builder shows a Home › Edit Home breadcrumb back to the launchpad.
- Global search — Cards are searchable from Filament's built-in global search, with the Section/Page/Space they live in shown as result details.
- Role-based visibility (Fiori-style permissions) — a "Permission" field on every Space/Page/Section/Card, softly integrated with
bezhansalleh/filament-shieldandspatie/laravel-permission. - Localization — English, European Portuguese, Brazilian Portuguese and generic Portuguese translations included, fully overridable.
Requirements
- PHP 8.2+
- Filament 5.0+ (4.0+ also supported)
- Laravel (Illuminate contracts/support) 11.0+, 12.0+, or 13.0+
Installation
Install the package via Composer:
composer require anselmocossa/filament-launchpad
Run the installer — it publishes the config, then optionally runs the package migrations:
php artisan launchpad:install # publish config php artisan launchpad:install --migrate # ...and run the migrations (asks first)
Prefer to do it by hand? The package auto-loads its migrations, so run them normally:
php artisan migrate
Optionally publish the config file, views or translations:
php artisan vendor:publish --tag="launchpad-config" php artisan vendor:publish --tag="launchpad-views" php artisan vendor:publish --tag="launchpad-lang"
Register the plugin in your panel provider:
use Filament\Launchpad\LaunchpadPlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugin(LaunchpadPlugin::make()); }
The plugin registers a Launchpad page at the panel root (/), so it becomes the panel home. If your panel also registers the default Dashboard page, remove it (or give it another slug) so the launchpad can own /.
After installing, build the launchpad from the UI: create a Space, a Page inside it, a Section inside that, and Cards inside the Section — or use the drag-and-drop builder (see below) instead of the plain forms.
Usage
Spaces, Pages, Sections and Cards
The launchpad is database-driven by default. The hierarchy is:
- Space — a top-level entry in the sub-nav. A Space with a single Page renders as a plain button; with several Pages it shows a dropdown.
- Page — lives inside a Space and holds Sections. Selecting a Page swaps which Sections render.
- Section — a titled group of Cards on a Page.
- Card — a single tile: KPI, Shortcut, or Widget.
Manage all four through the plugin's Filament Resources (Spaces, reachable from the sidebar; Pages, Sections and Cards reachable through relation managers and the "Pages"/"Cards" header buttons), or build a Page visually with the drag-and-drop builder.
KPI sources
A KPI card's live value comes from a KpiSource class that returns a rich KpiResult (value + unit + trend + badge) from a single query. Scaffold one with artisan:
php artisan make:launchpad-kpi SalesToday # → app/Filament/Launchpad/SalesTodayKpi.php ("Kpi" suffix added automatically) php artisan make:launchpad-kpi SalesToday --model=Order # → app/Filament/Launchpad/Order/SalesTodayKpi.php
namespace App\Filament\Launchpad; use App\Models\Order; use Filament\Launchpad\Launchpad\BaseKpiSource; use Filament\Launchpad\Launchpad\KpiResult; class SalesTodayKpi extends BaseKpiSource { public function cacheFor(): ?int { return 120; // seconds; null = no TTL cache (still memoized once per request) } public function resolve(): KpiResult { return KpiResult::make(Order::whereDate('created_at', today())->count()) ->unit('orders') ->trend('+3 today', 'success') // success | danger | gray ->badge('new'); // pass null to hide the badge } }
BaseKpiSource derives key() (sales_today) and label() (Sales Today) from the class name (the Kpi suffix is stripped). A KPI Card references the source by its key() from the "Source (Live)" select in the Card form; a source that throws degrades the tile to — instead of breaking the page. A Card without a source can still use a "Fixed Value" typed into the form.
Registration. Drop the class under app/Filament/Launchpad and it is auto-discovered — no wiring needed. Register explicitly (which turns auto-discovery off) or scan a custom folder instead:
use Filament\Launchpad\LaunchpadPlugin; LaunchpadPlugin::make() ->kpis([\App\Filament\Launchpad\SalesTodayKpi::class]) // explicit ->discoverKpis(in: app_path('Metrics'), for: 'App\\Metrics') // custom folder ->autoDiscoverKpis(false); // opt out entirely
The legacy closure form still works for quick one-offs and is fully backward-compatible:
LaunchpadPlugin::make()->kpiSources([ 'open_tickets' => fn () => Ticket::whereNull('resolved_at')->count(), ]);
Per-panel scoping. Override panels() to limit a source to specific panels (empty = all):
public function panels(): array { return ['store']; }
Multi-tenant caching. When cacheFor() is set, values are cached under the source's cacheKey() (defaults to key()). In a multi-tenant app, override it so a cached value never leaks across tenants:
public function cacheKey(): string { return static::key().':'.tenancy()->tenant()?->id; }
Sparkline
A KPI tile can draw a small chart in place of its trend line — an area (a line with a filled area) or bars. Pass the series, oldest value first:
return KpiResult::make(Candidate::count()) ->trend("+{$thisMonth} this month") ->sparkline($lastTwelveMonths); // area, in the launchpad accent color return KpiResult::make($open) ->sparkline($applicationsPerWeek, 'bar', '#16a34a'); // bars, in a color of your choice
The chart is inline SVG — no chart library, no JavaScript — and stretches to the tile's width. Without a color it uses the launchpad accent color (accent_color in config/launchpad.php or ->accentColor(), green by default). The trend text, when set, becomes the chart's tooltip. Only live sources draw one: a series isn't something an editor types into the card form.
Card library
Card Library presets show up in the builder's "Card Library" panel, ready to be dragged into any Section. Define each as a CardPreset class (auto-discovered under app/Filament/Launchpad) — scaffold one with artisan:
php artisan make:launchpad-card SalesToday
# → app/Filament/Launchpad/SalesTodayCard.php ("Card" suffix added automatically)
namespace App\Filament\Launchpad; use Filament\Launchpad\Launchpad\BaseCardPreset; class SalesTodayCard extends BaseCardPreset { protected string $title = 'Sales Today'; protected ?string $subtitle = 'Point of Sale'; protected ?string $icon = 'heroicon-o-banknotes'; protected string $type = 'kpi'; // kpi | shortcut | widget protected string $targetType = 'url'; // none | url | resource | page protected ?string $targetValue = '/admin/sales'; // protected ?string $kpiSource = 'sales_today'; // optionally pre-wire a live KPI source }
BaseCardPreset derives the key() from the class name (SalesTodayCard → sales_today). Registration mirrors KPI sources: auto-discovered by default, or register explicitly / scan a custom folder / opt out with ->cards([...]), ->discoverCards(in:, for:), ->autoDiscoverCards(false).
The legacy array form still works and is merged with the class-based presets (class presets win on key collision):
LaunchpadPlugin::make()->cardLibrary([ ['key' => 'sales-today', 'title' => 'Sales Today', 'type' => 'kpi', 'target_type' => 'url', 'target_value' => '/admin/sales'], ]);
Presets are reusable — dropping the same preset into several Sections is expected. A preset that names a kpi_source keeps it on the created Card, so the value is live straight away; otherwise wire one (or edit the value) afterwards.
Cards from other packages
A package can bring its own KPI sources and presets with LaunchpadPlugin::provide(), usually from its service provider's boot(). Unlike ->kpis() / ->cards(), it never turns the app's auto-discovery off, and on a key collision the app's own source or preset wins:
use Filament\Launchpad\LaunchpadPlugin; if (class_exists(LaunchpadPlugin::class)) { LaunchpadPlugin::provide( kpis: [\Vendor\Package\Launchpad\PendingKpi::class], cards: [ \Vendor\Package\Launchpad\PendingCard::class, // a CardPreset class fn (): array => [/* preset arrays, built when the library is shown */], ], ); }
A closure is called each time the library is shown, so it can depend on the current panel or user; one that throws is reported and skipped. Filament ChatAI uses this to offer its assistant tiles.
Widgets
Any widget already registered on the panel through widgets() or discoverWidgets() is automatically available in the builder's "Widgets" library. Use widgets() on the plugin only to override the generated label/icon/column span, or to expose a widget that is not registered on the panel:
use App\Filament\Widgets\RevenueChart; use Filament\Launchpad\LaunchpadPlugin; LaunchpadPlugin::make()->widgets([ [ 'key' => 'revenue-chart', 'class' => RevenueChart::class, 'label' => 'Revenue', 'icon' => 'heroicon-o-chart-bar', 'columnSpan' => 'full', ], ]);
Dropping a widget from the library creates a Card of type widget that stores only its registered key — the class is resolved from the developer's registration at render time, never read from the database directly.
Scaffold a launchpad-ready widget (a StatsOverviewWidget, no custom view required) with artisan:
php artisan make:launchpad-widget Revenue # → app/Filament/Launchpad/RevenueWidget.php php artisan make:launchpad-widget Revenue --model=Order # → app/Filament/Launchpad/Order/RevenueWidget.php
Drag-and-drop builder
Open the builder from a Page's Build action (Spaces → a Space → Pages → a Page), or jump straight to the home page's builder via Edit Home in the account/user menu. From there you can:
- Add, rename, reorder and delete Sections.
- Drag a preset from the Card Library, or a widget from the Widgets panel, into any Section.
- Drag existing Cards between Sections, or reorder them within a Section.
- Click a Card to edit it in place (same form used by the Cards relation manager).
Global search
Cards are registered as globally searchable (by title and subtitle) through Filament's built-in global search. A result shows which Section/Page/Space the Card lives in, and navigates to whatever the Card's target resolves to.
Navigation placement
By default the spaces and pages live in a full-width bar below the Filament topbar. Move them into the topbar itself — beside the panel logo, as native Filament topbar items and dropdowns — and drop the secondary bar:
LaunchpadPlugin::make() ->subNavigation(false) // hide the full-width bar below the topbar ->topbarNavigation(); // render spaces/pages beside the logo
Both accept a bool or a Closure. Left unset, the secondary bar stays on and the topbar navigation stays off, exactly as before.
The topbar navigation collapses on its own: spaces that don't fit beside the logo move to a "More" dropdown («Mais» in Portuguese), the active space always stays visible, and below 768 px the list becomes a single ☰ with every space and its pages. With both options above, a panel can also turn off Filament's own navigation (->navigation(false)); if it does, keep the topbar start visible on small screens so the logo and the ☰ show:
@media (max-width: 63.999rem) { .fi-topbar-start { display: flex; } }
Personal bookmarks ("Save to Home")
Let users pin any page of the panel to their own home — Fiori's "Save as Tile":
LaunchpadPlugin::make()->bookmarks();
Every page header then shows a Save to Home button (★). It adds a shortcut card to a personal My shortcuts section at the end of the user's home, named after the page and with its icon; on a saved page the button reads In Home, and pressing it again removes the card. The URL is kept with its query string, so a filtered list can be saved as it is.
Bookmarks are one user's own: the personal section only renders for its owner, and bookmark cards stay out of the shared card catalog, the Cards resource and the global search. The button is not shown on the launchpad home itself. Off by default.
The active space outside the launchpad
On the launchpad itself the active space comes from ?space=. On any other page — a resource opened from a card, say — the bar keeps the space you came from instead of falling back to the first one: the active space is the one with a card linking to the current page, and the open page is the one that holds that card.
- A card matches when its URL path equals the current path or is a parent of it (
/admin/ordersmatches/admin/orders/5/edit); the query string is ignored, and cards pointing at the panel root never match. - When several spaces have a matching card, the space you last chose wins (remembered in the session per panel); otherwise the longest matching path wins, and a tie goes to a space other than the first one.
- Only spaces, pages and cards the user may see take part, and no extra query is run.
Configuration
The published config file (config/launchpad.php) exposes branding, accent_color, dark_header, and tile_sizing:
return [ 'branding' => [ 'name' => 'Launchpad', 'logo' => null, ], 'accent_color' => '#16a34a', 'dark_header' => false, 'tile_sizing' => 'fluid', // 'fixed' (up to 6 per row, never below the tile width) or 'fluid' (stretched to fill, in even rows: 9 tiles = 5 + 4) 'tile_icon_tone' => 'muted', // 'muted' (pale gray icons) or 'strong' (dark icons) ];
fixed:repeat(6, 1fr)— always 6 equal columns, each card exactly 1/6 of row width, no empty space, no stretching.fluid:auto-fit minmax(176px, 1fr)— tiles stretch equally to fill the row, more tiles per row on wider screens.
Both modes use auto-fit so empty grid tracks collapse when fewer tiles than columns.
Prefer the fluent plugin API for anything beyond these — LaunchpadPlugin::make()->accentColor('#16a34a')->tileSizing('fixed') — since the config file cannot express closures (live KPIs, widget mappings).
Tile icon tone
The icons on the tiles are pale gray by default, so they stay in the background. Make them dark — close to the tile text — with 'strong':
LaunchpadPlugin::make() ->tileIconTone('strong'); // 'muted' (default) or 'strong'
The tone follows light and dark mode, applies to every KPI and shortcut tile and to the card previews in the builder and in Edit Home. Without a fluent call, tile_icon_tone in config/launchpad.php decides; unknown values fall back to 'muted'.
Permissions & Shield
Every Space, Page, Section and Card has a Permission field: the roles allowed to see it. Leave it empty and everyone can see the item — this is the default, unrestricted behaviour, with no dependency required.
Install bezhansalleh/filament-shield (which pulls in spatie/laravel-permission) to turn the field into an active gate:
composer require bezhansalleh/filament-shield spatie/laravel-permission php artisan shield:install
Once installed:
- A Space/Page/Section/Card restricted to one or more roles is hidden from any user who does not hold at least one of them — cascading down the hierarchy (an empty Space, Page or Section, once everything inside it is filtered out, is hidden too).
- The Shield
super_adminrole always sees everything, regardless of restrictions. - The
Launchpad(home) andEditHomepages are themselves gated (View:Launchpad,View:EditHome), and the Spaces/Pages/Sections/Cards Resources ship their own policies — Shield's convention-based policy discovery does not reach models underFilament\Launchpad\Models\*, so the plugin registers these policies itself. - A restricted Card is also excluded from Filament's global search results for users without the role.
Without spatie/laravel-permission installed, none of the above applies: the "Permission" field is simply not rendered, and every ability is granted.
Scoping selectable roles
The plugin deliberately does not assume how applications isolate roles. In a multi-tenant application, restrict the roles offered by the Permission field with a query callback on the panel plugin:
use Filament\Launchpad\LaunchpadPlugin; use Illuminate\Database\Eloquent\Builder; LaunchpadPlugin::make() ->visibilityRolesQuery(fn (Builder $query) => $query ->where('tenant_id', tenancy()->tenant()?->id));
The callback applies consistently to Spaces, Pages, Sections and Cards, including the card editor in the drag-and-drop builder. It receives the role model query, so applications can use any suitable isolation rule (tenant, organisation, guard, status, and so on). Leaving it unset preserves the plugin's existing behaviour.
Requiring a permission on every item
By default the Permission field is optional, and an empty field means everyone can see. That is the right default where the launchpad is a convenience layer over a panel people already reached by other means.
It is the wrong default where the launchpad is the way in and every item is meant to belong to somebody. There, saving a space with the field blank quietly publishes it to the whole installation, and nothing on screen says so. Turn the field mandatory:
use Filament\Launchpad\LaunchpadPlugin; LaunchpadPlugin::make() ->visibilityRolesRequired();
A closure decides case by case — useful when the same codebase serves a primary panel that authors a shared template and tenant panels where every item has an owner:
LaunchpadPlugin::make() ->visibilityRolesRequired(fn (): bool => tenancy()->tenant() !== null);
The switch reaches Spaces, Pages, Sections and Cards — every item that shares the field — and the placeholder and hint change with it, so the form stops promising that an empty field lets everyone see. Left unset (the default), nothing changes on upgrade.
Hiding the management resources entirely
Policies answer "may this person manage spaces?". They cannot answer "does this installation include Launchpad at all?" — a licence tier, a feature flag, an activated module. Applications that sell their panel in parts can gate the four management resources with a host predicate:
use Filament\Launchpad\LaunchpadPlugin; LaunchpadPlugin::make() ->resourceAccess(fn (string $resource): bool => $tenant->hasModule('core'));
The callback receives the resource's fully-qualified class name, so a single
closure can answer for SpaceResource, PageResource, SectionResource and
CardResource — or treat them differently.
This predicate runs in addition to the resource's policy, never instead of it: it can hide a resource, never expose one the policy would have refused. Leaving it unset keeps every resource reachable, exactly as before. A predicate that throws degrades to "allowed" rather than taking the panel down.
Localization
Translation catalogs are included for:
- English (
en) — base language - Portuguese (
pt) - European Portuguese (
pt_PT) - Brazilian Portuguese (
pt_BR)
Publish and override the translation files with:
php artisan vendor:publish --tag="launchpad-lang"
Then edit resources/lang/vendor/launchpad/{locale}/launchpad.php — every string in the plugin (labels, buttons, builder copy, model names) is wrapped in __('launchpad::launchpad.*'), so any published locale immediately overrides the package default.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Issues and pull requests are welcome. Please run composer lint and composer test before submitting a PR, and keep new strings translatable via launchpad::launchpad.*.
Security Vulnerabilities
If you discover any security-related issues, please email anselmokossa.apk@gmail.com instead of using the issue tracker.
Credits
License
The MIT License (MIT). Please see License File for more information.

