Organization dashboard surfaces
Organization surfaces belong to one tenant and run under a resolved CurrentOrganization. A plugin may contribute management links, home widgets, both, or neither.
Management pages and submenu
Implement ContributesDashboard:
public function dashboards(): array
{
return [
new PluginDashboard(
id: 'orders-overview',
title: 'Overview',
description: 'Tenant order metrics.',
routeName: 'plugin.orders.dashboard.overview',
icon: DashboardIcon::Chart,
order: 40,
requiresEnabled: true,
),
new PluginDashboard(
id: 'orders',
title: 'Orders',
description: 'Manage tenant orders.',
routeName: 'plugin.orders.dashboard.orders.index',
icon: DashboardIcon::Inbox,
order: 41,
requiresEnabled: true,
),
];
}
All contributions from one plugin become one sidebar submenu. The manifest name labels the parent; the first ordered item supplies its icon. Add overview/index and useful fixed-path shortcuts. Keep parameterized show/edit/delete routes contextual.
Use this route pattern:
Route::middleware([
'web',
'auth',
'organization.route',
'organization.member',
'plugin.available:orders',
])
->prefix('/charity/{organization:slug}/plugins/orders/manage')
->name('plugin.orders.dashboard.')
->group(function (): void {
Route::get('/', OverviewController::class)->name('overview');
Route::get('/orders', [OrderController::class, 'index'])->name('orders.index');
});
requiresEnabled: true requires plugin.available:{slug}. false requires plugin.available:{slug},installed; definition validation enforces the pairing. Settings remain at /charity/{organization}/plugins/{slug}, so management must use /manage.
Organization-home widgets
Implement ContributesDashboardWidgets:
public function dashboardWidgets(Organization $organization): array
{
return [
new PluginDashboardWidget(
id: 'orders-overview',
view: view('plugin.orders::widgets.overview', [
'organization' => $organization,
'openOrders' => Order::query()->where('status', 'open')->count(),
]),
order: 20,
),
];
}
Widgets render on charity.dashboard, are globally sorted by order, and contain only ID, pre-built View, and order. Keep them summary-oriented and link to the full management page.
Tenant safety
- Every tenant model uses
BelongsToOrganization. - Never accept
organization_idfrom a form. - Resolve
organization.routebefore model binding or queries. - Authorize direct routes; hiding a link is not authorization.
- Test two organizations, guessed ULIDs, disabled/unavailable state, and direct requests.
Fundraising is the complete reference: seven submenu pages and one organization-home widget.