Admin Extensions
Packages extend the admin through CapellAdmin, admin bridges, tagged contracts, settings contributors, and Filament classes.
Admin Bridges
Section titled “Admin Bridges”Use an admin bridge when a package contributes more than one admin concern. The bridge keeps package wiring in one class and lets Capell boot the package’s admin surface once.
use Capell\Admin\Contracts\Bridges\AdminBridge;use Capell\Admin\Data\Bridges\AdminBridgeContextData;use Capell\Admin\Support\Bridges\AdminBridgeRegistrar;
final class ExampleAdminBridge implements AdminBridge{ public function register(AdminBridgeRegistrar $registrar, AdminBridgeContextData $context): void { $registrar->resource(ExampleResource::class, group: 'content', name: 'examples'); $registrar->page(ExampleReportPage::class); $registrar->dashboardWidget(ExampleHealthWidget::class, DashboardEnum::SystemHealth); $registrar->settingsClass('example', ExampleSettings::class); $registrar->settingsSchema('example', ExampleSettingsSchema::class); }}Register the bridge from the package admin provider:
CapellAdmin::registerAdminBridge('capell-app/example', ExampleAdminBridge::class);CapellAdmin::bootAdminBridges('capell-app/example');For one small contribution, direct CapellAdmin::contributeToAdminSurface(...) calls are still acceptable. Prefer a bridge when the package adds a resource plus settings, widgets, extenders, or user menu actions.
Register package pages through an admin bridge or directly with AdminSurfaceContributionData::page(...):
use Capell\Admin\Data\AdminSurfaceContributionData;
CapellAdmin::contributeToAdminSurface( AdminSurfaceContributionData::page(ExampleReportPage::class),);The page class should provide translated navigation labels:
public static function getNavigationLabel(): string{ return __('capell-example::navigation.example_report');}If the page is the package’s main settings or control page, register it as the extension page:
CapellAdmin::registerExtensionPage('capell-app/example', ExampleSettingsPage::class);This registers the Filament page and lists the package on the Extensions management page with a direct Edit action. Extension pages do not keep their own direct sidebar item; Capell automatically adds accessible registered extension pages to the grouped Filament sub-navigation on the Extensions page.
Resources
Section titled “Resources”Register resources when the package owns a model:
use Capell\Admin\Data\AdminSurfaceContributionData;
CapellAdmin::contributeToAdminSurface( AdminSurfaceContributionData::resource(ExampleResource::class, group: 'content', name: 'examples'),);The group and name pair is the lookup slot other package code can use through CapellAdmin::getResource($group, $name). Use a stable name instead of relying on class names when a package wants to replace or extend a known resource slot.
Dashboard Widgets
Section titled “Dashboard Widgets”Use dashboard slots rather than hardcoded admin registration:
CapellAdmin::registerDashboardWidget(ExampleHealthWidget::class, DashboardEnum::SystemHealth);Widgets should implement Capell\Admin\Contracts\CapellWidgetContract when they participate in Capell dashboard settings.
User Menu Items
Section titled “User Menu Items”Use the user menu registry for admin-only package shortcuts and attention counts:
CapellAdmin::registerUserMenuItem( key: 'capell-example.notes', label: fn (): string => __('capell-example::user-menu.notes'), url: fn (): string => route('filament.admin.pages.example-notes'), badge: fn (): int => ExampleNote::query()->unread()->count(), sort: 40,);See User Menu Registry for the full API, badge rules, and translated package examples.
Welcome Tour Steps
Section titled “Welcome Tour Steps”Packages can add onboarding steps to the Capell admin welcome tour:
CapellAdmin::registerWelcomeTourStep( key: 'capell-example.feature', title: __('capell-example::welcome.feature_title'), description: __('capell-example::welcome.feature_description'), element: '.capell-example-feature', sort: 80,);Use stable admin-only selectors for element. Omit element for a modal step. The tour is shown on the admin dashboard and can be enabled or disabled per user from the user form.
Dashboard Settings
Section titled “Dashboard Settings”Packages that add widgets should register them through registerDashboardWidget() and make sure Admin settings can expose their toggles. Small counters that belong inside the Capell overview widget should use registerOverviewStat() instead of a standalone widget.
For package settings screens, use SettingsSchemaRegistry directly or the bridge registrar helpers:
$registrar->settingsClass('example', ExampleSettings::class);$registrar->settingsSchema('example', ExampleSettingsSchema::class);$registrar->settingsMetadata(new SettingsGroupMetadata( group: 'example', label: 'capell-example::settings.label',));Settings pages can extend AbstractPackageSettingsPage and use SettingsGroupMetadata for their page label, icon, and sort.
Form And Table Extenders
Section titled “Form And Table Extenders”Use tagged extenders when adding fields or actions to core resources:
$this->app->tag([ExamplePageSchemaExtender::class], PageSchemaExtender::TAG);Common tags:
| Need | Tag or registry |
|---|---|
| Page form fields, tabs, sidebar components, relation managers | PageSchemaExtender::TAG |
| Site form fields, tabs, create wizard fields, relation managers | SiteSchemaExtender::TAG |
| Layout tabs and relation managers | LayoutSchemaExtender::TAG |
| User fields, sidebar components, relation managers | UserSchemaExtender::TAG or a UserResourceBridge |
| User table columns, filters, record actions, toolbar actions | UserTableExtender::TAG |
| Page table columns, filters, bulk actions, query changes | PageTableExtender::TAG |
| Page header actions | PageHeaderActionExtender::TAG |
| Site header actions | SiteHeaderActionExtender::TAG |
| Site table row actions | SiteRecordActionExtender::TAG |
| Header actions on arbitrary resource pages | ResourceHeaderActionExtender::TAG |
| Page title/slug field actions or after-label schema | PageTitleWithSlugInputExtender::TAG |
| Page edit form actions or header widgets | PageEditExtender::TAG |
| Page/site export modal fields and options | PageExportExtender::TAG |
| Publish panel sections | PublishPanelExtender::TAG |
| Media edit header actions | MediaEditActionExtender::TAG |
| Extensions page status content | ExtensionsPageExtender::TAG |
| Filament panel configuration | AdminPanelExtender::TAG |
| Admin header tools | AdminToolItem::TAG |
Prefer extenders over modifying admin resources directly.
Use the abstract schema extenders when possible:
AbstractPageSchemaExtenderAbstractSiteSchemaExtenderAbstractUserSchemaExtender
They provide no-op defaults so a package only overrides the hooks it needs. See Schema Hooks for method signatures, hook enums, and resolver debugging.
Page Table Status
Section titled “Page Table Status”The main Pages table renders one core publish/workflow status column. Admin owns the column layout; packages that provide workflow semantics should replace the resolver instead of adding a competing status column.
Bind Capell\Admin\Contracts\Pages\PageTableStatusResolver from the package admin provider:
use Capell\Admin\Contracts\Pages\PageTableStatusResolver;use Capell\Admin\Data\Pages\PageTableStatusData;use Capell\Core\Models\Page;use Filament\Support\Icons\Heroicon;use Illuminate\Database\Eloquent\Builder;
$this->app->singleton(PageTableStatusResolver::class, ExampleWorkflowPageStatusResolver::class);
final class ExampleWorkflowPageStatusResolver implements PageTableStatusResolver{ /** * @param Builder<Page> $query * @return Builder<Page> */ public function modifyQuery(Builder $query): Builder { return $query->with('latestWorkflowStep'); }
public function resolve(Page $page): PageTableStatusData { return new PageTableStatusData( label: __('capell-example::workflow.awaiting_review'), shortLabel: __('capell-example::workflow.review_short'), tooltip: __('capell-example::workflow.awaiting_review_tooltip'), color: 'info', icon: Heroicon::OutlinedClipboardDocumentCheck, ); }}Core falls back to publish-date states: deleted, expired, scheduled, and published. Approval, draft, rollback, and workspace states belong in workflow packages through this resolver.
Extensions Dashboard
Section titled “Extensions Dashboard”/admin/extensions is the Extensions dashboard. Keep package management pages in the Filament left sub-navigation by registering them with CapellAdmin::registerExtensionPage($packageName, PageClass::class).
Use dashboard widgets for operational package status, health checks, shortcuts, and marketplace-style actions that belong on the overview. Register widgets against the Extensions dashboard scope:
use Capell\Admin\Enums\DashboardEnum;use Capell\Admin\Facades\CapellAdmin;
CapellAdmin::registerDashboardWidget( ExampleExtensionHealthWidget::class, DashboardEnum::Extensions,);Admin bridges can use the convenience method:
$registrar->extensionDashboardWidget(ExampleExtensionHealthWidget::class);Every extension dashboard widget must use a globally unique settingsKey(), preferably package-prefixed, so dashboard customisation can enable, disable, reorder, and resize it without colliding with core widgets.
Extension dashboard widgets may implement Capell\Admin\Contracts\Extensions\ExtensionDashboardWidgetContract when they want to expose their package-author metadata explicitly. The contract defines the widget settings key, label, description, default span, default order, dashboard scope, and canView() gate. Existing CapellWidgetContract widgets remain supported; the contract is for packages that want their dashboard contribution to be self-describing.
Packages can also contribute operation data without coupling to the Filament UI. Register providers from an admin bridge:
$registrar->extensionHealthProvider(ExampleHealthProvider::class);$registrar->extensionRuntimeCheckProvider(ExampleRuntimeProvider::class);$registrar->extensionQuickActionProvider(ExampleQuickActionProvider::class);$registrar->extensionUpdateMetadataProvider(ExampleUpdateProvider::class);$registrar->extensionDependencyProvider(ExampleDependencyProvider::class);Provider contracts live in Capell\Admin\Contracts\Extensions:
| Contract | Use |
|---|---|
ExtensionHealthProvider | Adds package health alerts to the diagnostics surface. |
ExtensionRuntimeCheckProvider | Adds runtime compatibility checks such as queues, cache stores, services, or package-specific gates. |
ExtensionQuickActionProvider | Adds safe package-level operational shortcuts. Keep destructive work permission-gated. |
ExtensionUpdateMetadataProvider | Supplies update readiness states when metadata comes from a package or marketplace integration. |
ExtensionDependencyProvider | Adds uninstall, disable, or update blockers beyond Capell core package protection. |
Core catches health provider failures and records a warning diagnostic for the affected package instead of breaking the dashboard. Marketplace integration remains optional; core widgets work from local manifests, installed package data, extension records, runtime gates, and health alerts.
Extensions Page Actions
Section titled “Extensions Page Actions”Use Capell\Admin\Support\Extensions\ExtensionsPageActionRegistry when a package needs to add a command to the Extensions page.
Register header actions for package-level work such as installing example content, syncing metadata, or opening a setup flow. Register table actions only when the action belongs to a specific extension row.
The Extensions page already provides core row actions such as documentation and uninstall where available, so packages should not duplicate those actions.
See How To Create A Capell Extension for the full package authoring flow and code examples.
Extensions Page Contributions
Section titled “Extensions Page Contributions”Optional packages should extend the core Extensions page instead of registering a competing package manager page.
Use ExtensionsPageActionRegistry for header actions that open modal workflows:
resolve(ExtensionsPageActionRegistry::class)->registerHeaderAction( fn (ExtensionsPage $page): Action => Action::make('examplePackageAction') ->label(__('capell-example::actions.example')) ->modalContent(view('capell-example::filament.extensions.example-modal')),);Use ExtensionsPageExtender::TAG for status alerts or explanatory blocks shown in the Extensions dashboard actions area. Keep package-specific business logic inside the package contributor. New operational surfaces should prefer an Extensions dashboard widget.
Marketplace connection UI should stay action-oriented:
- When the site is not connected, show the connection state and the Connect Capell account action.
- When the site is connected, do not render a success alert above the table. Show Open Marketplace as the primary header action.
- Open Marketplace opens the marketplace browser in a Filament modal from
/admin/extensions, keeping installed extensions and marketplace browsing in one workflow. - Do not use a large success alert for the connected state. The account link is a means to access Marketplace, not the destination.
- Marketplace catalogue API calls must request JSON and use a sort value the Capell app API supports. The safe default is
recommended; Capell app also accepts the browser sort valuesfeatured_latest,latest,price_low,price_high, andname.
Navigation
Section titled “Navigation”Use Filament page methods for labels, icons, groups, and sort order. Store labels in package translation files.
If a package uses a custom parent navigation group from getNavigationGroup(), register that group from the package admin provider:
use Capell\Admin\Enums\NavigationGroupPositionEnum;use Capell\Admin\Facades\CapellAdmin;use Filament\Support\Icons\Heroicon;
CapellAdmin::registerNavigationGroup( label: 'capell-example::navigation.example', icon: Heroicon::OutlinedSquares2X2, position: NavigationGroupPositionEnum::After, relativeTo: 'capell-admin::navigation.group_content',);Capell resolves translated labels before merging, so multiple packages can register the same group independently without creating duplicate sidebar groups. Position can be Start, End, Before, or After; Before and After require relativeTo.
Debugging Admin Extensions
Section titled “Debugging Admin Extensions”If an admin contribution is missing, start with Extension Troubleshooting. The common fixes are:
- confirm the package admin provider is loaded;
- confirm the bridge was registered and booted for the package name;
- run
php artisan capell:admin-clear-cache; - run
php artisan capell:admin-cache-configuratorsfor configurators and schema extenders; - run
php artisan capell:admin-cache-widgetsfor widgets; - re-run
php artisan capell:admin-installwhen permissions or policies changed.