Dashboard Widgets
Modules can provide custom dashboard widgets that display real-time data. This guide covers creating widget data providers and registering them with the system.
Overview
The widget system consists of:
- Widget Definitions - Metadata about the widget (name, size, category, config schema)
- Widget Data Providers - Backend classes that fetch and format data
- Widget Registry - Central registry that manages widget registration
Creating a Widget Data Provider
1. Create the Provider Class
Create a class that extends BaseWidgetDataProvider:
<?php
namespace Modules\YourModule\App\WidgetProviders;
use App\Core\Services\BaseWidgetDataProvider;
use Modules\YourModule\App\Models\YourModel;
class YourWidgetProvider extends BaseWidgetDataProvider
{
/**
* Cache TTL in seconds (default: 300 = 5 minutes)
*/
protected int $cacheTtl = 300;
/**
* Optional: Config schema for validation
*/
protected array $configSchema = [
'type' => 'object',
'properties' => [
'showTrend' => ['type' => 'boolean'],
'limit' => [
'type' => 'integer',
'minimum' => 1,
'maximum' => 100,
],
],
];
/**
* Unique widget identifier
*/
public static function getWidgetId(): string
{
return 'yourmodule:your-widget';
}
/**
* Fetch and return widget data
*/
public function getData(array $config = [], array $context = []): array
{
$dateRange = $this->getDateRange($context);
$limit = $config['limit'] ?? 10;
// Query your data
$currentStats = $this->getStats($dateRange['start'], $dateRange['end']);
// Get previous period for comparison
$periodDays = $dateRange['start']->diffInDays($dateRange['end']);
$previousStats = $this->getStats(
$dateRange['start']->copy()->subDays($periodDays),
$dateRange['start']->copy()->subDay()
);
return [
'total' => [
'value' => $currentStats['total'],
'formatted' => $this->formatNumber($currentStats['total']),
'change' => $this->calculateChange(
$currentStats['total'],
$previousStats['total']
),
],
'items' => $this->getRecentItems($limit),
'period' => [
'start' => $dateRange['start']->toIso8601String(),
'end' => $dateRange['end']->toIso8601String(),
],
];
}
protected function getStats($start, $end): array
{
return YourModel::query()
->whereBetween('created_at', [$start, $end])
->selectRaw('COUNT(*) as total, SUM(amount) as revenue')
->first()
->toArray();
}
protected function getRecentItems(int $limit): array
{
return YourModel::query()
->latest()
->limit($limit)
->get(['id', 'name', 'amount', 'created_at'])
->toArray();
}
}
2. Register in module.json
Add the provider to your module's configuration:
{
"backend": {
"widgetDataProviders": {
"yourmodule:your-widget": "Modules\\YourModule\\App\\WidgetProviders\\YourWidgetProvider"
}
}
}
The system will automatically register the provider when your module loads.
Base Provider Features
BaseWidgetDataProvider provides several helpful features:
Caching
Data is automatically cached based on the $cacheTtl property:
protected int $cacheTtl = 300; // 5 minutes
// Or disable caching
protected int $cacheTtl = 0;
Cache keys are generated based on widget ID, config, and context.
Date Range Helpers
$dateRange = $this->getDateRange($context);
// Returns: ['start' => Carbon, 'end' => Carbon]
Number Formatting
$this->formatNumber(1234567); // "1.2M"
$this->formatNumber(12345); // "12.3K"
$this->formatNumber(123); // "123"
Change Calculation
$change = $this->calculateChange($current, $previous);
// Returns: ['value' => 12.5, 'direction' => 'up']
Tenant Context
$tenantId = $this->getTenantId($context);
Config Validation
Define a JSON Schema-like $configSchema to validate widget configuration:
protected array $configSchema = [
'type' => 'object',
'properties' => [
'refreshInterval' => [
'type' => 'integer',
'minimum' => 30,
'maximum' => 3600,
],
'displayMode' => [
'type' => 'string',
'enum' => ['compact', 'detailed', 'chart'],
],
'showLegend' => [
'type' => 'boolean',
],
],
'required' => ['displayMode'],
];
Supported validation rules:
| Rule | Description |
|---|---|
type |
string, number, integer, boolean, array, object |
required |
Array of required property names |
enum |
Array of allowed values |
minimum / maximum |
Number range |
minLength / maxLength |
String length |
pattern |
Regex pattern |
format |
email, url, date, uuid, color |
minItems / maxItems |
Array length |
properties |
Nested object validation |
Widget API Endpoints
The dashboard module provides these API endpoints:
Get Widget Data
GET /api/v1/widgets/data/{widgetId}
Query params:
config- Widget configuration (JSON)dateRange- Date range context
Batch Data Fetch
POST /api/v1/widgets/data/batch
Body:
{
"widgets": [
{ "widgetId": "orders:overview", "config": {} },
{ "widgetId": "customers:stats", "config": { "limit": 5 } }
],
"dateRange": {
"start": "2024-01-01",
"end": "2024-01-31"
}
}
Refresh Widget Data
POST /api/v1/widgets/data/{widgetId}/refresh
Forces a cache refresh and returns fresh data.
Validate Config
POST /api/v1/widgets/config/{widgetId}/validate
Body:
{
"config": {
"displayMode": "chart",
"showLegend": true
}
}
Get Config Schema
GET /api/v1/widgets/config/{widgetId}/schema
Returns the widget's configuration schema.
Custom Widget Categories
Register custom categories for organizing widgets:
{
"backend": {
"widgetCategories": {
"yourmodule": {
"label": "Your Module",
"icon": "Box",
"description": "Widgets for Your Module analytics"
}
}
}
}
Default categories include:
analytics- Data visualization and metricsorders- Order tracking and managementcustomers- Customer insightsinventory- Stock and product managementfinance- Revenue and financial dataworkflows- Automation metrics
Best Practices
- Use Caching - Always set an appropriate
$cacheTtlfor production - Handle Empty Data - Return sensible defaults when no data exists
- Validate Config - Define
$configSchemafor complex configurations - Use Context - Respect date ranges and tenant context from
$context - Format Numbers - Use
formatNumber()for large values - Include Trends - Use
calculateChange()for period-over-period comparisons
Example: Orders Overview Widget
<?php
namespace Modules\Orders\App\WidgetProviders;
use App\Core\Services\BaseWidgetDataProvider;
use Modules\Orders\App\Models\Order;
class OrdersOverviewProvider extends BaseWidgetDataProvider
{
protected int $cacheTtl = 300;
public static function getWidgetId(): string
{
return 'orders:overview';
}
public function getData(array $config = [], array $context = []): array
{
$dateRange = $this->getDateRange($context);
$stats = Order::query()
->whereBetween('created_at', [$dateRange['start'], $dateRange['end']])
->selectRaw('
COUNT(*) as total_orders,
COALESCE(SUM(total_amount), 0) as total_revenue,
COALESCE(AVG(total_amount), 0) as avg_order_value,
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN status = ? THEN 1 ELSE 0 END) as shipped
', ['pending', 'shipped'])
->first();
return [
'totalOrders' => [
'value' => $stats->total_orders,
'formatted' => $this->formatNumber($stats->total_orders),
],
'totalRevenue' => [
'value' => $stats->total_revenue,
'formatted' => '₹' . $this->formatNumber($stats->total_revenue),
],
'avgOrderValue' => [
'value' => $stats->avg_order_value,
'formatted' => '₹' . number_format($stats->avg_order_value, 2),
],
'pendingOrders' => $stats->pending,
'shippedOrders' => $stats->shipped,
];
}
}
Next Steps
- Module Structure - Complete module configuration
- Workflow Nodes - Create automation triggers and actions
- CLI Commands - Generate module scaffolding