Custom Dashboards
FeaturedLet your users build and share their own dashboards with a drag-and-drop interface. Define your data sources in PHP and let them do the rest.
Author:
Filament
Documentation
- Demo
- Introduction
- Installation
- Understanding widget data sources
- Configuration
- Getting started
- Features
- Creating widget data sources
- Attribute types
- Accessing related attributes
- Authorization
- Sharing dashboards
- Supported widget types
- Advanced features
- Example: Complete widget data source
- Migrations
- Reference
- Support
Beta: This plugin is currently in beta. Breaking changes are not expected but could occur.
#Demo
See custom dashboards in action and build your own at cd-demo.filamentphp.com.
#Introduction
The Custom Dashboards Plugin allows you to create data-driven dashboards without writing custom widget code. Instead of creating separate widget classes for each chart or metric, you define widget data sources that describe your data, and the plugin provides configurable widgets (charts, stats, tables) that users can customize through a user interface.
This approach is ideal when:
- You want to give users control over their own dashboards without writing widget code for each variation
- You need multiple views of the same data without duplicating code
- You want to provide flexible analytics and reporting capabilities to your users
#Installation
First, install the Custom Dashboards Plugin using Composer:
Once you have purchased a license for the Custom Dashboards Plugin, install it via Composer:
composer config repositories.filament composer https://packages.filamentphp.com/composer
composer config --auth http-basic.packages.filamentphp.com "YOUR_EMAIL_ADDRESS" "YOUR_LICENSE_KEY"
composer require filament/custom-dashboards-plugin:"^1.0@beta"
Note: Windows Powershell may ignore
^characters in version constraints. If you encounter issues, usecomposer require filament/custom-dashboards-plugin:"~1.0@beta"instead.
Then run the install command:
php artisan filament-cd:install
The install command will guide you through the setup process:
- Publish and run migrations - Creates the necessary database tables
- Create a theme - If your panel doesn't have a theme, offers to create one using
php artisan make:filament-theme - Add CSS import - Adds the plugin's stylesheet import to your theme
- Rebuild assets - Compiles the theme with the new styles
- Register the plugin - Adds the plugin to your panel provider
- Generate data sources - Optionally creates widget data sources from your existing resources
If you have multiple panels, specify which panel to install to:
php artisan filament-cd:install --panel=admin
Note: To access your purchased license, sign in to Filament Packages with the email address used to purchase your Custom Dashboards Plugin license.
#Manual installation
If you prefer to install manually or need to customize the setup, follow these steps:
#1. Publish and run migrations
php artisan filament-cd:publish-migrations
php artisan migrate
#2. Add the CSS import to your theme
Add the following @import to your Filament theme CSS file (for example, resources/css/filament/admin/theme.css):
@import '../../../../vendor/filament/custom-dashboards-plugin/resources/css/index.css';
If you don't have a theme yet, create one first:
php artisan make:filament-theme
Then rebuild your assets:
npm run build
#3. Register the plugin
Add the plugin to your panel provider:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugins([
CustomDashboardsPlugin::make()
->discoverDataSources(
in: app_path('Filament/Widgets/DataSources'),
for: 'App\\Filament\\Widgets\\DataSources'
),
]);
}
See the Configuration section below for additional options.
Important: After each plugin update, you should publish and run any new migrations. The plugin tracks migrations semantically, so this is always safe to run: it will skip anything already published or executed. See the Migrations section for details and how to automate this with Composer.
#Understanding widget data sources
Widget data sources are PHP classes that provide data to dashboard widgets. They act as intermediaries between your application's data (typically Eloquent models) and the various chart and widget types.
Each widget data source defines:
- Model: The Eloquent model that provides the data
- Attributes: The fields available for charting and display
- Authorization: Who can access the data
Widget data sources extend the EloquentWidgetDataSource class and implement methods that describe your data structure. The plugin uses this information to provide configuration options to users and to query the appropriate data.
#Configuration
To register the plugin, add it to your panel configuration:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
use Filament\Panel;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(
CustomDashboardsPlugin::make()
);
}
#Registering widget data sources
You may register widget data sources manually:
use App\Filament\Widgets\DataSources\CustomerWidgetDataSource;
use App\Filament\Widgets\DataSources\OrderWidgetDataSource;
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->widgetDataSources([
OrderWidgetDataSource::class,
CustomerWidgetDataSource::class,
])
Alternatively, you may discover widget data sources automatically:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->discoverDataSources(
in: app_path('Filament/Widgets/DataSources'),
for: 'App\\Filament\\Widgets\\DataSources'
)
#Registering custom widgets
In addition to the configurable widgets provided by the plugin (charts, stats, tables), you may create and register your own custom widgets. These are widgets with fixed data and behavior defined in code that users can add to their dashboards.
You may register custom widgets manually:
use App\Filament\Widgets\RecentOrdersWidget;
use App\Filament\Widgets\TopCustomersWidget;
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->widgets([
RecentOrdersWidget::class,
TopCustomersWidget::class,
])
Alternatively, you may discover custom widgets automatically:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->discoverWidgets(
in: app_path('Filament/Widgets'),
for: 'App\\Filament\\Widgets'
)
#Getting started
To create your first widget data source, use the Artisan command with the --generate flag to automatically create attributes from your database schema:
php artisan make:filament-cd-widget-data-source Order --generate
This will create a widget data source class in app/Filament/Widgets/DataSources/OrderWidgetDataSource.php with attributes automatically generated from your Order model's database schema.
Users can now create dashboards and add widgets based on your Order data through the dashboard UI. Users with appropriate permissions can:
- Create and customize their own dashboards
- Add charts, stats, and tables using your widget data sources
- Configure filters and aggregations without writing code
#Features
The Custom Dashboards Plugin provides:
- User-created dashboards: Users can create and customize their own dashboards through the UI with drag-and-drop editing
- Role-based access: Control dashboard access with read, write, and owner permissions, plus sharing capabilities
- Flexible widgets: Charts (line, bar, pie, doughnut, polar, scatter), stats, and tables configured through widget data sources
- Powerful filtering: Query builder integration for complex data filtering on any widget
#Creating widget data sources
#Using the command
You may create a widget data source using the make:filament-cd-widget-data-source command:
php artisan make:filament-cd-widget-data-source Order
This will create a new widget data source class in app/Filament/Widgets/DataSources/OrderWidgetDataSource.php.
If you wish to automatically generate attributes from your database schema, use the --generate or -G flag:
php artisan make:filament-cd-widget-data-source Order --generate
#Creating data sources from resources
If you have existing Filament resources, you may generate widget data sources for all of them at once:
php artisan make:filament-cd-resource-widget-data-sources --generate
This command will create a widget data source for each resource in your panel, automatically detecting the model and generating appropriate attributes. It will also automatically set the $group and $sort properties based on the resource's getNavigationGroup() and getNavigationSort() methods, ensuring your widget data sources are organized the same way as your resources in the navigation.
#Defining attributes
Each widget data source must define a getAttributes() method that returns an array of attributes:
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\EloquentWidgetDataSource;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\NumberAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\DateAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\TextAttribute;
class OrderWidgetDataSource extends EloquentWidgetDataSource
{
protected ?string $model = Order::class;
public function getAttributes(): array
{
return [
NumberAttribute::make('amount'),
NumberAttribute::make('quantity'),
DateAttribute::make('created_at'),
TextAttribute::make('status'),
];
}
}
#Grouping and sorting widget data sources
You may organize widget data sources in the data source select dropdown using groups and sort order. This is helpful when you have many data sources and want to categorize them or control their display order.
When generating widget data sources from resources using the make:filament-cd-resource-widget-data-sources command, the $group and $sort properties are automatically set based on the resource's navigation settings.
#Grouping data sources
Use the $group property to group related data sources together in the select dropdown:
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\EloquentWidgetDataSource;
class OrderWidgetDataSource extends EloquentWidgetDataSource
{
protected ?string $model = Order::class;
protected ?string $group = 'Sales';
// ...
}
You may also use an enum that implements a getLabel() method, similar to navigation groups:
use App\Enums\DataSourceGroup;
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\EloquentWidgetDataSource;
class OrderWidgetDataSource extends EloquentWidgetDataSource
{
protected ?string $model = Order::class;
protected string | UnitEnum | null $group = DataSourceGroup::Sales;
// ...
}
<?php
namespace App\Enums;
enum DataSourceGroup: string
{
case Sales = 'sales';
case Analytics = 'analytics';
case Reports = 'reports';
public function getLabel(): string
{
return match ($this) {
self::Sales => 'Sales',
self::Analytics => 'Analytics',
self::Reports => 'Reports',
};
}
}
#Sorting data sources
Use the $sort property to control the order of data sources within their group (or globally if ungrouped):
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\EloquentWidgetDataSource;
class OrderWidgetDataSource extends EloquentWidgetDataSource
{
protected ?string $model = Order::class;
protected ?string $group = 'Sales';
protected ?int $sort = 10;
// ...
}
Data sources are sorted by their $sort value in ascending order. Lower values appear first. Data sources without an explicit sort value (null) appear at the end. The sort order also determines which group appears first in the dropdown - the lowest sort value across all data sources in a group determines that group's position.
#Attribute types
The plugin provides several attribute types for different kinds of data:
#Number attribute
NumberAttribute is used for numeric fields such as integers, decimals, and floats:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\NumberAttribute;
NumberAttribute::make('price')
->label('Price')
#Formatting as money
You may format a number attribute as money using the money() method:
NumberAttribute::make('price')
->money('usd')
The first parameter is the currency code. You may also customize the divisor, locale, and decimal places:
NumberAttribute::make('price')
->money(
currency: 'usd',
divideBy: 100, // Convert cents to dollars
locale: 'en_US',
decimalPlaces: 2,
)
#Customizing decimal formatting
You may customize how decimals are formatted:
NumberAttribute::make('rating')
->decimalPlaces(2)
->decimalSeparator(',')
->thousandsSeparator('.')
Alternatively, you may set a maximum number of decimal places to show:
NumberAttribute::make('rating')
->maxDecimalPlaces(2)
#Setting the locale
You may set the locale for number formatting:
NumberAttribute::make('amount')
->locale('de_DE')
#Date attribute
DateAttribute is used for date, datetime, and timestamp fields:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\DateAttribute;
DateAttribute::make('created_at')
->label('Created at')
#Including time
By default, date attributes do not include a time component. You may enable this for timestamp fields:
DateAttribute::make('created_at')
->time()
#Allowing past and future dates
By default, date attributes allow past dates but not future dates. You may customize this:
DateAttribute::make('scheduled_at')
->past(false)
->future(true)
#Text attribute
TextAttribute is used for string and text fields:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\TextAttribute;
TextAttribute::make('name')
->label('Name')
#Using enums
You may associate a text attribute with a PHP enum:
use App\Enums\OrderStatus;
TextAttribute::make('status')
->enum(OrderStatus::class)
This will provide better filtering options and display the enum's label in widgets.
#Boolean attribute
BooleanAttribute is used for boolean fields:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\BooleanAttribute;
BooleanAttribute::make('is_active')
->label('Active')
#Relationship attribute
RelationshipAttribute is used for Eloquent relationships:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\RelationshipAttribute;
RelationshipAttribute::make('customer')
->label('Customer')
->titleAttribute('name')
The titleAttribute() method specifies which attribute of the related model should be used for display.
#Multiple relationships
For HasMany or BelongsToMany relationships, use the multiple() method:
RelationshipAttribute::make('tags')
->multiple()
->titleAttribute('name')
#Optional relationships
For relationships that may not exist, use the emptyable() method:
RelationshipAttribute::make('manager')
->emptyable()
->titleAttribute('name')
#Customizing labels
You may customize the label of any attribute:
NumberAttribute::make('total_amount')
->label('Total amount')
#Making attributes nullable
All attribute types support nullable values:
NumberAttribute::make('discount')
->nullable()
#Custom value formatting
You may format attribute values using a closure:
NumberAttribute::make('price')
->formatValueUsing(fn ($value) => '$' . number_format($value, 2))
#Custom filter constraints
You may customize how an attribute is filtered in widgets using the queryBuilderConstraint() method:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\NumberAttribute;
use Filament\QueryBuilder\Constraints\TextConstraint;
use Filament\QueryBuilder\Constraints\Operators\IsFilledOperator;
NumberAttribute::make('amount')
->queryBuilderConstraint(
fn () => TextConstraint::make('amount')
->operators([
IsFilledOperator::make()
->label('Has a value'),
IsFilledOperator::make()
->label('Does not have a value')
->inverse(),
])
)
#Accessing related attributes
You may access attributes from related models using dot notation:
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\NumberAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\TextAttribute;
public function getAttributes(): array
{
return [
TextAttribute::make('customer.name')
->label('Customer name'),
NumberAttribute::make('customer.credit_limit')
->label('Customer credit limit'),
];
}
This allows you to display and filter by related model attributes in your widgets.
#Authorization
Widget data sources can control who has access to the data. By default, the plugin checks the model policy's viewAny method:
public function canAccess(): bool
{
return get_authorization_response('viewAny', $this->getModel())->allowed();
}
You may override the canAccess() method to implement custom authorization logic:
public function canAccess(): bool
{
return auth()->user()?->can('viewOrderAnalytics');
}
If you wish to skip authorization entirely, override the shouldSkipAuthorization() method:
public function shouldSkipAuthorization(): bool
{
return true;
}
#Sharing dashboards
By default, dashboards can be shared with individual users. The plugin supports three access roles:
- Owner: Full control including editing, sharing, and deleting the dashboard
- Write: Can edit widgets and configuration but cannot share or delete
- Read: Can only view the dashboard
#User sharing
#Disabling user sharing
You may disable user sharing using the userSharing() method:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->userSharing(false)
When user sharing is disabled and no custom shareable models are registered, the share button will be hidden entirely.
#Disabling default role
By default, dashboard owners can set a default role that grants all users access to a dashboard. In a multi-tenant or SaaS context, you may want to prevent users from changing this. Use the defaultRole() method:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->defaultRole(false)
This hides the default role field from the create and settings forms. Dashboards that already have a default role set (e.g. via seeding or migration) will continue to honor it — this setting only controls the UI, not the underlying behavior.
You may also pass a closure to resolve the condition at runtime, for example based on the current user:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
use Filament\Facades\Filament;
CustomDashboardsPlugin::make()
->defaultRole(fn () => Filament::auth()->user()?->is_admin)
#Scoping shareable users
By default, all users are available in the share dropdown. You may restrict which users can be selected by providing a scope closure using the scopeUserSharingUsing() method. The closure receives the query builder and the currently authenticated user:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
use Illuminate\Database\Eloquent\Builder;
CustomDashboardsPlugin::make()
->scopeUserSharingUsing(
fn (Builder $query, $user) => $query->where('team_id', $user->team_id)
)
This scope only affects the share dropdown in the UI. If a user has already been shared with and no longer matches the scope, they will still appear in their own dropdown row so their role can be changed or they can be removed. Once removed and saved, they will no longer be available for re-selection.
#Teams and organizations
You may extend the sharing system to support teams, organizations, or any other model. This allows users to share dashboards with entire groups rather than individual users.
#Step 1: Implement the interface
Add the CanReceiveSharedDashboards interface to your model:
<?php
namespace App\Models;
use Filament\CustomDashboardsPlugin\Contracts\CanReceiveSharedDashboards;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;
class Team extends Model implements CanReceiveSharedDashboards
{
public static function getDashboardShareableLabel(): string
{
return 'Team';
}
public static function getDashboardShareableTitleAttribute(): string
{
return 'name';
}
public static function resolveDashboardShareablesForUser(Authenticatable $user): ?Relation
{
return $user->teams();
}
public static function getDashboardShareableOptionsQuery(Authenticatable $user): Builder
{
return $user->teams()->getQuery();
}
}
The interface requires four methods:
getDashboardShareableLabel()- The label shown in the share UI (e.g., "Team", "Organization")getDashboardShareableTitleAttribute()- The attribute used for display in dropdowns (e.g., "name")resolveDashboardShareablesForUser()- Returns a relation to determine which of this model the user belongs to. Used for efficient access checks via subqueries. Returnnullif the user has no access to any.getDashboardShareableOptionsQuery()- Returns a query builder for loading options in the share dropdown. The first 50 results are shown initially, with search available for more.
#Step 2: Register the model
Register your shareable models in the plugin configuration:
use App\Models\Team;
use App\Models\Organization;
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
CustomDashboardsPlugin::make()
->shareableModels([
Team::class,
Organization::class,
])
#Customizing share options
The getDashboardShareableOptionsQuery() method controls which options appear in the share dropdown. You may customize this based on your application's requirements:
// Only show teams the user owns
public static function getDashboardShareableOptionsQuery(Authenticatable $user): Builder
{
return static::query()->where('owner_id', $user->getKey());
}
// Show all teams (for admins)
public static function getDashboardShareableOptionsQuery(Authenticatable $user): Builder
{
return static::query();
}
// Show teams from the user's organization
public static function getDashboardShareableOptionsQuery(Authenticatable $user): Builder
{
return static::query()->where('organization_id', $user->organization_id);
}
#Access resolution
When a user accesses a dashboard, the plugin checks all possible access routes:
- Direct user assignment
- Assignment via any registered shareable model (teams, organizations, etc.)
- Default role (if configured on the dashboard)
If the user has access through multiple routes (e.g., directly and via a team), the highest role is used.
#Supported widget types
The plugin provides several configurable widget types. Each widget type supports different attribute types depending on its purpose.
#Stats widget
The stats widget displays one or more statistic cards with aggregated data.
Configuration options:
- Metric: Select the aggregation method (count, sum, average, min, max)
- Value field: Choose which attribute to aggregate (only shown when the metric requires it)
- Supports: Number attributes and relationship attributes (must be multiple or emptyable)
- Filters: Apply filters to the data using query builder
When using the count metric, you do not need to select a value field.
#Line chart widget
The line chart widget displays data over time or across continuous values.
Configuration options:
- Time & Grouping > Show data over: Choose the dimension to display on the horizontal axis
- Supports: Date attributes, number attributes, and relationship attributes (must be multiple or emptyable)
- Time & Grouping > Date range type: For date attributes, choose between rolling window or specific dates
- Time & Grouping > Time period: For date attributes, select a preset range or define a custom period
- Measurement > Metric: Select the aggregation method (count, sum, average, min, max)
- Measurement > Value field: Choose which attribute to measure (only shown when the metric requires it, label changes based on selected metric)
- Supports: Number attributes and relationship attributes (must be multiple or emptyable)
- Measurement > Show as running total: Enable cumulative mode to display running totals, where each data point shows the sum of all previous values plus the current value (only shown when the metric is count or sum)
- Filters: Apply filters to the data using query builder
#Bar chart widget
The bar chart widget displays categorical data as vertical bars.
Configuration options:
- Grouping > Group by: Choose the dimension to display as categories
- Supports: Date attributes, number attributes, boolean attributes, text attributes (with enum), and relationship attributes (must be multiple or emptyable)
- Grouping > Display mode: For relationship attributes with a title attribute, select how to display the relationship
- Show related items separately: Display the related item's title (e.g., "Customer A", "Customer B")
- Count how many items are related: Show the count of related items (for multiple relationships only, e.g., "2", "5")
- Show relationship presence: Show whether the relationship exists as "Yes" or "No" (for emptyable relationships only)
- Measurement > Metric: Select the aggregation method (count, sum, average, min, max)
- Measurement > Value field: Choose which attribute to measure (only shown when the metric requires it, label changes based on selected metric)
- Supports: Number attributes only
- Filters: Apply filters to the data using query builder
Understanding relationship display modes:
The Display mode field appears when you select a relationship attribute. The available modes depend on your relationship configuration:
- For multiple relationships (
HasMany,BelongsToMany):- If you defined a title attribute: Choose between "Show related items separately" or "Count how many items are related" mode
- If no title attribute: Only "Count how many items are related" mode is available
- For singular relationships (
HasOne,BelongsTo):- If you defined a title attribute and the relationship is emptyable: Choose between "Show related items separately" or "Show relationship presence" mode
- If you defined a title attribute but the relationship is not emptyable: Only "Show related items separately" mode is available
- If no title attribute but the relationship is emptyable: Only "Show relationship presence" mode is available
When using "Show relationship presence" mode, the chart displays "Yes" for records where the relationship exists and "No" for records where it doesn't.
#Pie chart widget
The pie chart widget displays proportional data as slices of a circle.
Configuration options:
- Grouping > Group by: Choose which attribute to use for the pie slices
- Supports: Date attributes, number attributes, boolean attributes, text attributes (with enum), and relationship attributes (must be multiple or emptyable)
- Grouping > Display mode: For relationship attributes with a title attribute, select how to display the relationship
- Show related items separately: Display the related item's title (e.g., "Customer A", "Customer B")
- Count how many items are related: Show the count of related items (for multiple relationships only, e.g., "2", "5")
- Show relationship presence: Show whether the relationship exists as "Yes" or "No" (for emptyable relationships only)
- Measurement > Metric: Select the aggregation method (count, sum, average, min, max)
- Measurement > Value field: Choose which attribute to measure (only shown when the metric requires it, label changes based on selected metric)
- Supports: Number attributes only
- Filters: Apply filters to the data using query builder
The Display mode field works the same way as described in the bar chart widget section. When using "Show relationship presence" mode with emptyable relationships, pie slices will be labeled "Yes" and "No".
#Doughnut chart widget
The doughnut chart widget is similar to the pie chart but with a hole in the center. It has the same configuration options and relationship display modes as the pie chart widget.
#Polar area chart widget
The polar area chart widget displays data in a circular layout with varying segment sizes. It has the same configuration options and relationship display modes as the pie chart widget.
#Scatter chart widget
The scatter chart widget displays data points on an X-Y axis, useful for showing correlations between two attributes.
Configuration options:
- Time & Grouping > X-axis field: Choose the dimension to display on the horizontal axis
- Supports: Date attributes, number attributes, and relationship attributes (must be multiple or emptyable)
- Time & Grouping > Date range type: For date attributes, choose between rolling window or specific dates
- Time & Grouping > Time period: For date attributes, select a preset range or define a custom period
- Measurement > Metric: Select the aggregation method (count, sum, average, min, max), or leave empty to display individual points
- Measurement > Y-axis field: Choose which attribute to measure (only shown when the metric requires it)
- Supports: Number attributes and relationship attributes (must be multiple or emptyable)
- Filters: Apply filters to the data using query builder
When the metric is left empty, the scatter chart will display individual data points without aggregation.
#Table widget
The table widget displays data in a tabular format.
Configuration options:
- Attribute: Choose which attributes to display as columns
- Supports: All attribute types (including relationship attributes with a title attribute)
- Display mode: For relationship attributes, select how to display the relationship
- Show related items separately: Display the related item's title (e.g., "Customer A")
- Count how many items are related: Show the count of related items (for multiple relationships only, e.g., "3")
- Show relationship presence: Show whether the relationship exists as a checkmark or dash (for emptyable relationships only)
- Filters: Apply filters to the data using query builder
Users can add multiple columns to the table, each with its own attribute and configuration.
Understanding relationship display modes in tables:
The Display mode field works similarly to charts, with the same logic for determining available modes based on whether the relationship is multiple and/or emptyable. In table columns:
- Show related items separately: Displays the value of the title attribute from the related item
- Count how many items are related: Displays the number of related items as an integer
- Show relationship presence: Shows a visual indicator (checkmark for "Yes", dash for "No") rather than the text "Yes"/"No"
#Advanced features
#Automatic attribute generation
When using the --generate flag, the plugin automatically:
- Detects foreign keys and creates relationship attributes
- Identifies enum casts and applies them to text attributes
- Recognizes money/price fields and applies money formatting
- Sets appropriate decimal places for decimal columns
- Skips password, token, and auto-increment fields
#Customizing query builder constraints
By default, the plugin generates query builder constraints from your widget data source's attributes. Each attribute is automatically converted to an appropriate constraint based on its type.
You may override the getQueryBuilderConstraints() method to define completely custom constraints:
use Filament\QueryBuilder\Constraints\DateConstraint;
use Filament\QueryBuilder\Constraints\NumberConstraint;
use Filament\QueryBuilder\Constraints\NumberConstraint\Operators\IsMinOperator;
use Filament\QueryBuilder\Constraints\NumberConstraint\Operators\IsMaxOperator;
use Filament\QueryBuilder\Constraints\TextConstraint;
public function getQueryBuilderConstraints(): array
{
return [
TextConstraint::make('status')
->label('Order status'),
NumberConstraint::make('amount')
->label('Order amount')
->operators([
IsMinOperator::make(),
IsMaxOperator::make(),
]),
DateConstraint::make('created_at')
->label('Created at'),
];
}
This allows you to:
- Define custom constraint labels
- Limit which operators are available for specific constraints
- Exclude certain attributes from filtering
- Add constraints that don't correspond to attributes
- Control the order in which constraints appear
If you have a large number of constraints, you may also customize the constraint picker columns by overriding the getQueryBuilderConstraintPickerColumns() method:
public function getQueryBuilderConstraintPickerColumns(): array | int
{
return 3; // Display constraints in 3 columns
}
By default, the plugin displays constraints in 1 column for up to 10 constraints, 2 columns for 11-20 constraints, and 3 columns for more than 20 constraints.
#Relationship display modes
Relationship attributes support multiple display modes when used in widgets. The available modes depend on whether the relationship is multiple (HasMany, BelongsToMany) or singular (HasOne, BelongsTo), and whether it is emptyable. For detailed information about how relationship display modes work in each widget type, see the individual widget sections above.
#Creating custom widgets
Custom widgets allow you to create widgets that users can add to their dashboards without needing to configure a widget data source. These widgets have their data and behavior defined in code, making them perfect for specific use cases like "Recent Orders" or "Top Customers".
To create a custom widget, extend any Filament widget class and use the InteractsWithCustomDashboards trait:
<?php
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\Concerns\InteractsWithCustomDashboards;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class RecentOrdersWidget extends StatsOverviewWidget
{
use InteractsWithCustomDashboards;
protected function getStats(): array
{
return [
Stat::make('Total Orders', Order::query()->count()),
Stat::make('Pending Orders', Order::query()->where('status', 'pending')->count()),
Stat::make('Revenue', '$' . number_format(Order::query()->sum('total'), 2)),
];
}
public static function getCustomDashboardLabel(): string
{
return 'Recent Orders';
}
public static function getCustomDashboardDescription(): ?string
{
return 'Overview of recent order statistics';
}
}
The InteractsWithCustomDashboards trait provides the following methods you can customize:
getCustomDashboardId()- The identifier stored in the database (defaults to the widget's fully-qualified class name). You may want to override this to avoid storing PHP class names in your database.getCustomDashboardLabel()- The display name shown to users when selecting widgets.getCustomDashboardDescription()- Optional description displayed in the widget picker.canAccess()- Authorization logic to control who can use the widget.
#Adding configuration to custom widgets
Custom widgets can include a configuration form that allows users to customize the widget when they add it to their dashboard. To add configuration, you need to:
- Create a configuration model to store the settings
- Create a migration for the configuration table
- Define the configuration form
- Use the configuration in your widget
Step 1: Create the configuration model
Create a model to store the widget configuration:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RecentOrdersWidgetConfiguration extends Model
{
protected $guarded = [];
protected $table = 'recent_orders_widget_configs';
protected $casts = [
'show_pending' => 'boolean',
'show_completed' => 'boolean',
];
}
Step 2: Create the migration
Create a migration to store the configuration:
php artisan make:migration create_recent_orders_widget_configs_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('recent_orders_widget_configs', function (Blueprint $table): void {
$table->id();
$table->boolean('show_pending')->default(true);
$table->boolean('show_completed')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('recent_orders_widget_configs');
}
};
Step 3: Define the configuration form and model
Update your widget to include the configuration form:
<?php
namespace App\Filament\Widgets;
use App\Models\Order;
use App\Models\RecentOrdersWidgetConfiguration;
use Filament\CustomDashboardsPlugin\Widgets\Concerns\InteractsWithCustomDashboards;
use Filament\Forms\Components\Checkbox;
use Filament\Schemas\Schema;
use Filament\Widgets\StatsOverviewWidget;
use Illuminate\Database\Eloquent\Model;
class RecentOrdersWidget extends StatsOverviewWidget
{
use InteractsWithCustomDashboards;
public static function configureCustomDashboardConfigurationForm(Schema $schema): Schema
{
return $schema->schema([
Checkbox::make('show_pending')
->label('Show pending orders')
->default(true),
Checkbox::make('show_completed')
->label('Show completed orders')
->default(true),
]);
}
public static function getCustomDashboardConfigurationModel(): ?string
{
return RecentOrdersWidgetConfiguration::class;
}
// ...
}
When users add this widget to their dashboard, they will be prompted to configure the time period and which statistics to display. The configuration is automatically saved to the database and loaded when the widget is rendered.
Step 4: Accessing the configuration
Within your widget, access the configuration through $this->dashboardWidget->configuration:
use App\Models\Order;
use App\Models\RecentOrdersWidgetConfiguration;
use Filament\Widgets\StatsOverviewWidget\Stat;
protected function getStats(): array
{
$configuration = $this->dashboardWidget->configuration;
assert($configuration instanceof RecentOrdersWidgetConfiguration);
$stats = [];
$stats[] = Stat::make('Total Orders', Order::query()->count());
if ($configuration->show_pending) {
$stats[] = Stat::make('Pending Orders', Order::query()->where('status', 'pending')->count());
}
if ($configuration->show_completed) {
$stats[] = Stat::make('Completed Orders', Order::query()->where('status', 'completed')->count());
}
return $stats;
}
The configuration is automatically saved to the model when the user submits the form, and loaded when the widget is displayed.
#Customizing the resource navigation item
You may customize the resource navigation item (the "Custom Dashboards" link in the sidebar) using the navigationItem() method. The closure receives the default NavigationItem and should return the modified item:
use Filament\CustomDashboardsPlugin\CustomDashboardsPlugin;
use Filament\Navigation\NavigationItem;
CustomDashboardsPlugin::make()
->navigationItem(fn (NavigationItem $item) => $item
->sort(3)
->group('Tools')
)
Any properties you don't modify will keep their defaults. For example, the URL and active-state detection are always preserved unless you explicitly override them.
#Embedding dashboards in panel pages
You can embed dashboards directly into any Filament panel page, allowing users to add widgets above and below the page content. This is useful for customizing pages like resource List pages with contextual dashboards.
To enable embedded dashboards on a page, add the HasEmbeddedDashboards interface and the InteractsWithEmbeddedDashboards trait to your page class, then add EditEmbeddedDashboardsAction::make() to the header actions:
use App\Filament\Resources\Orders\OrderResource;
use Filament\Actions\CreateAction;
use Filament\CustomDashboardsPlugin\Actions\EditEmbeddedDashboardsAction;
use Filament\CustomDashboardsPlugin\Concerns\InteractsWithEmbeddedDashboards;
use Filament\CustomDashboardsPlugin\Contracts\HasEmbeddedDashboards;
use Filament\Resources\Pages\ListRecords;
class ListOrders extends ListRecords implements HasEmbeddedDashboards
{
use InteractsWithEmbeddedDashboards;
protected static string $resource = OrderResource::class;
protected function getHeaderActions(): array
{
return [
EditEmbeddedDashboardsAction::make(),
CreateAction::make(),
];
}
}
Once configured, users can click the "Edit embedded dashboards" action to enter edit mode and add widgets above and below the page content. The widgets are saved per-page and persist across sessions.
#Customizing the embedded dashboards' component identifier
By default, the plugin uses the page's fully-qualified class name (FQN) to identify which page the embedded dashboard belongs to. If you prefer not to store PHP class names in your database, you may override the getEmbeddedDashboardComponent() method to return a custom identifier of your choice:
public function getEmbeddedDashboardComponent(): string
{
return 'orders.list';
}
#Example: Complete widget data source
Here is a complete example of a widget data source for an order model:
<?php
namespace App\Filament\Widgets\DataSources;
use App\Enums\OrderPriority;
use App\Enums\OrderStatus;
use App\Models\Order;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\EloquentWidgetDataSource;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\DateAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\NumberAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\RelationshipAttribute;
use Filament\CustomDashboardsPlugin\Widgets\DataSources\Attributes\TextAttribute;
class OrderWidgetDataSource extends EloquentWidgetDataSource
{
protected ?string $model = Order::class;
public function getAttributes(): array
{
return [
NumberAttribute::make('amount')
->label('Amount')
->money('usd', divideBy: 100),
NumberAttribute::make('quantity')
->label('Quantity'),
DateAttribute::make('created_at')
->label('Created at'),
DateAttribute::make('completed_at')
->label('Completed at')
->nullable(),
TextAttribute::make('status')
->label('Status')
->enum(OrderStatus::class),
TextAttribute::make('priority')
->label('Priority')
->enum(OrderPriority::class),
RelationshipAttribute::make('customer')
->label('Customer')
->emptyable()
->titleAttribute('name'),
TextAttribute::make('customer.name')
->label('Customer name'),
NumberAttribute::make('customer.credit_limit')
->label('Customer credit limit')
->money('usd', divideBy: 100),
];
}
}
This widget data source provides:
- Monetary attributes with proper formatting
- Date attributes with nullable support
- Enum-based text attributes for status and priority
- A relationship to the customer model
- Access to related customer attributes
Users can now create charts, stats, and tables using this data source without writing any additional code.
#Migrations
#How the migration system works
The plugin uses a semantic migration tracking system rather than relying on filenames. Each plugin migration is tagged with an internal identifier (e.g. create_filament_cd_dashboards_table), and the first time you publish migrations, an extra filament_cd column is added to your migrations table to store these identifiers.
This means:
- Renaming migration files is safe. Tracking is based on the semantic identifier, not the filename. You can rename published migration files without confusing the system.
- Re-publishing after updates is safe. The publish command checks the
filament_cdcolumn to determine which migrations have already run and skips them automatically, even if the filenames have changed between versions. - Minor and patch upgrades won't break your app if you forget to publish new migrations right away. The plugin checks whether a migration has actually been executed before depending on the tables or columns it creates, so missing a publish step won't cause runtime errors: it will simply mean new features that rely on those migrations won't be available until you publish and run them.
#Publishing migrations after updates
Every time you update the plugin to a new version, you should publish any new migrations:
php artisan filament-cd:publish-migrations
php artisan migrate
The filament-cd:publish-migrations command will:
- Scan the plugin's migrations
- Skip any that have already been published or run
- Copy only the new migrations into your
database/migrationsdirectory with fresh timestamps - Optionally run
php artisan migratefor you
You can also use the --force flag to republish all migrations, which is useful if you need to reset a migration file to its original state:
php artisan filament-cd:publish-migrations --force
#Automating migration publishing with Composer
To avoid forgetting this step, you can add the publish command to your Composer post-update-cmd scripts so that new migrations are published every time you run composer update:
{
"scripts": {
"post-update-cmd": [
"@php artisan filament-cd:publish-migrations --no-interaction"
]
}
}
If you already have other post-update-cmd scripts, just append this lines to the existing array. The publish command is safe to run repeatedly: it will skip any migrations that have already been published or run.
#Reference
#Metric options
When configuring charts and stats, users can select from several metric types:
- Count: Count the number of records (does not require a value field)
- Sum: Sum the values of a numeric attribute
- Average: Calculate the average of a numeric attribute
- Min: Find the minimum value of a numeric attribute
- Max: Find the maximum value of a numeric attribute
Only the count and sum metrics support "Show as running total" mode in line charts. When enabled, this mode displays running totals, where each data point shows the accumulated sum of all previous values plus the current value. For example, if your data points are 5, 3, 7, the cumulative chart would show 5, 8 (5+3), 15 (5+3+7).
#Date grouping and ranges
For widgets with date dimensions, users can group data by various time units:
- Second, Minute, Hour, Day, Month, Quarter, Year, Decade
The plugin provides convenient date range presets:
- Past: past minute, past hour, past week, past 2 weeks, past month, past quarter, past 6 months, past year, past 2 years, past 5 years, past decade
- Present: this minute, this hour, today, this month, this quarter, this year, this decade
- Future: next minute, next hour, next week, next 2 weeks, next month, next quarter, next 6 months, next year, next 2 years, next 5 years, next decade
Users may also select a custom absolute date range.
#Filtering
All widgets support filtering through Filament's QueryBuilder component. Filters are automatically generated from the widget data source's attributes and saved with the widget configuration.
The type of filter constraints available depends on the attribute type:
- Text attributes: equals, contains, starts with, ends with, is filled, is not filled
- Number attributes: equals, not equals, greater than, less than, between, is filled, is not filled
- Date attributes: equals, before, after, between, is filled, is not filled
- Boolean attributes: is true, is false, is filled, is not filled
- Relationship attributes: equals, contains (for multiple), is filled, is not filled
#Support
If you encounter any issues or have suggestions for improving Custom Dashboards, please open an issue or discussion on the Custom Dashboards Issues GitHub repository. You can get an invite to this repository by adding your GitHub username to your customer dashboard at packages.filamentphp.com. If you have account, purchase, or license-related questions, please email support@filamentphp.com.
The author
From the same author
Spatie Tags
Filament support for Spatie's Laravel Tags package.
Author:
Filament
Spatie Google Fonts
Filament support for Spatie's Laravel Google Fonts package.
Author:
Filament
Spatie Settings
Filament support for Spatie's Laravel Settings package.
Author:
Filament
Spatie Media Library
Filament support for Spatie's Laravel Media Library package.
Author:
Filament