Extension points
Slots
PluginSlot: home_after_hero, page_after_content, before_footer. Each maps to slots.{name}.v1. A plugin declaring a slot must require that capability. Priorities are manifest integers 0..1000, default 100; lower first, slug breaks ties.
Dashboards
interface ContributesDashboard
{
public function dashboards(): array;
}
new PluginDashboard(
id: 'events',
title: 'Events',
description: 'Manage events.',
routeName: 'plugin.events.dashboard.index',
icon: DashboardIcon::Calendar,
order: 30,
requiresEnabled: false,
);
IDs are kebab-case and unique per plugin. Routes must exist and be globally unique among contributions. PluginDashboardResolver::groupsForOrganization() collects dashboards from effective plugins, rejects runtime duplicates or missing routes, sorts each plugin's items by order, and sorts groups by their first item's order. forOrganization() flattens that grouped order; it does not globally re-sort items across plugins.
Dashboard contributions from the same plugin are grouped into one sidebar submenu. Put index/overview and useful fixed-path shortcuts in dashboards(); Events intentionally includes its create page. Parameterized show/edit/delete actions remain contextual. The parent label comes from the manifest name and its icon from the first ordered child.
DashboardIcon is enum-backed rather than plugin-provided SVG. Available cases are Inbox, Calendar, Grid, Chart, Heart, CreditCard, Users, CheckCircle, and Folder.
Organization-home widgets
interface ContributesDashboardWidgets
{
public function dashboardWidgets(Organization $organization): array;
}
new PluginDashboardWidget(
id: 'fundraising-overview',
view: view('plugin.fundraising::widgets.overview', $data),
order: 10,
);
PluginDashboardWidgetResolver::forOrganization() walks enabled compatible plugins, calls dashboardWidgets() when supported, deduplicates by {plugin}:{widget-id}, and sorts widgets globally by order. The value contains only a validated kebab-case ID, a pre-built View, and order; it has no title, description, or icon. Widgets render on the organization dashboard and should summarize and link to a full plugin dashboard rather than duplicate management workflows.
Super Admin
ContributesSuperAdminPages returns PluginSuperAdminPage navigation descriptors for global platform routes. PluginSuperAdminPageResolver::groupsForPlatform() includes only installed and globally active plugins. Routes use /admin/plugins/{slug}/manage, plugin.{slug}.admin.*, auth, super.admin, and plugin.available:{slug},installed.
ContributesSuperAdminWidgets returns ordered PluginSuperAdminWidget views for admin.dashboard. These contributions are global, theme-independent, and never depend on an organization assignment.
See Organization dashboard surfaces, Super Admin pages and widgets, and Public pages and theme integration for complete recipes.
Pages
interface ContributesPages
{
public function pages(): array;
public function renderPage(
PluginPage $page,
PluginRenderContext $context,
array $settings,
array $parameters = [],
): PluginPageResponse;
}
PluginPage takes id, slug, title, showInNavigation = true, layout = Contained, order = 100. Dynamic whole-segment placeholders cannot be direct navigation. PluginPageResolver sorts navigation, resolves a matching page, calls the driver, renders the view, and wraps it in ResolvedPluginPage.
Collisions
Validation rejects duplicate dashboard IDs/routes, missing routes, dashboard/settings URL collisions, availability middleware inconsistent with requiresEnabled, duplicate page IDs, equivalent cross-plugin patterns (events/{a} vs events/{b}), and plugin route prefixes that are not plugin.{slug}.. Enabled dashboards require plugin.available:{slug}; installed-mode dashboards require plugin.available:{slug},installed. Enable validation blocks plugin patterns matching existing tenant CMS slugs. Runtime guards duplicate effective patterns.
Super Admin validation additionally requires plugin.{slug}.admin.*, /admin/plugins/{slug}/manage, auth, super.admin, installed-mode availability, and no organization middleware.
Multi-surface example
Events combines two organization dashboard links, a full-width navigable index, contained dynamic detail, priority-40 home slot, package CRUD, and tenant data. Contact Form combines one organization dashboard, contact-us, site navigation, before-footer rendering, and throttled submissions. Fundraising combines seven organization pages, organization and Super Admin widgets, three read-only Super Admin pages, public pages, private proof submission, and tenant-scoped reporting.
Current limits: no arbitrary slots, tenant placement/priority controls, catch-all/optional page parameters, plugin dependency API, or functional disabled-dashboard visibility. Both themes apply Contained versus FullWidth in their plugin-page partials.