Skip to content

Extension Point API Reference

Use this page when you know what you want to extend and need the exact contract, registration point, and fallback behavior. Start with Extension point chooser if you are still deciding.

Registration uses two complementary mechanisms: contract tags for focused contributors, and AdminBridgeRegistrar for grouped admin integration. AdminBridgeRegistry is the internal read side, not a package-authoring write path. See Package provider conventions before adding provider wiring or a new registry.

Machine-readable stability and ownership are defined by the extension surface catalogue. Use its stable IDs in compatibility decisions and package contract failures.

Admin surface and Frontend render-hook contributions use the shared extension ordering policy for stable keys, relative positions, ownership-aware collisions, and boot-time freeze behaviour.

Register a typed AdminZoneContributionData through AdminBridgeRegistrar::zone() and position it with the ExtensionPosition value object. Stable zones cover Page edit content, form actions, and header widgets; Page list table columns; and the Extensions dashboard’s content before/after the table, header actions/widgets, and table columns/filters/record actions. Contributions may declare a Gate permission and a context-aware visibility callback; each zone validates values before they reach the screen.

The existing focused extenders remain compatible. Page list table columns returned by PageTableExtender are appended after stable-zone columns and cannot participate in stable-key ordering; new integrations should use the stable zone when ordering relative to built-in columns matters.

ColumnMeaning
APIContract, registry, facade, or tag to use.
Register fromWhere package code should usually register it.
Called byRuntime surface that consumes the contribution.
Safe fallbackWhat should happen when the package is absent, disabled, or returns nothing.
Test recipeSmallest useful proof.
NeedAPIRegister fromCalled bySafe fallbackTest recipe
Page subject typeCapellCore::registerPageType(new PageTypeData(...))Runtime provider registeringPackage() or boot methodBlueprint/page type selectionType is absent from selectorsAssert CapellCore::getPageTypes() contains the key.
Model morph aliasCapellCore::registerModels()Runtime providerMorph map and model resolutionLaravel default morph behaviorAssert morph map resolves the model alias.
Model behaviorCapellCore::registerModelInterceptor()Runtime/admin providerCapell model interceptor resolverBase model behavior remains unchangedInstantiate model and assert interceptor method affects only matching context.
Settings schemaProvider surface() or AdminBridgeRegistrarRuntime/admin provider or AdminBridgeAdmin settings surfacesGroup/tab is absentResolve registry and assert schema/settings class for group.
Package settings pageCapellAdmin::registerExtensionPage()AdminBridge or admin providerInstalled Extensions page and grouped Filament sub-navigationNo package control pageAssert ExtensionPageRegistry::get($packageName) returns the page and the sub-navigation contains accessible registered pages grouped by product group.
Content graph extractionContentGraphRegistry::TAG or registry registrationRuntime providerContent graph buildersContent is not linked into graphBuild graph for fixture content and assert edge exists.
Link picker entriesLinkableContentRegistry::register(...)Runtime/admin providerLink picker/search UIItem type is not searchableAssert registry includes provider and search returns fixture.
Renderable definitionRenderableRegistry::register(...)Runtime providerRendering/runtime buildersDefinition is unavailableAssert registry returns the definition key and any viewDataResolver returns explicit view variables.
Tailwind assetsTailwindAssetsRegistry::registerSource() / registerImport()Runtime/frontend providerFrontend/admin asset commandsPackage classes are not scannedAssert toReport() includes source/import origin.
Vendor asset conditionVendorAssetConditionRegistry::register(...)Runtime providerAsset manifest buildersAsset is not conditionally loadedAssert condition returns expected value for fixture context.
Developer makerMakerRegistryInterface::register(...)Runtime/dev providerMaker commandsMaker command option is absentAssert maker appears in registry.

The receipt contract covers the canonical methods behind these entries, including Capell\Core\Support\Renderables\RenderableRegistry::register(), FrontendComponentRegistry::register(), FrontendAssetsService::registerAsset(), and FrontendResponseRendererRegistry::register() / registerClass(). The Admin manager’s direct widget, navigation, welcome-tour, report, extension-page, workspace, overview-stat, activity-link, and asset methods are likewise receipt boundaries; bridge methods delegate to them and do not emit a second receipt.

NeedAPIRegister fromCalled bySafe fallbackTest recipe
Multiple admin surfacesAdminBridge with AdminBridgeRegistrarRuntime/admin providerAdmin bridge registryPackage contributes no admin surfacesAssert bridge is registered for package name.
Single admin page/resource/widgetCapellAdmin::contributeToAdminSurface(AdminSurfaceContributionData::...)Admin providerCapellAdminPluginSurface is absentAssert admin surface lookup contains the class.
Dashboard Filament widgetCapellAdmin::registerDashboardFilamentWidget(...)Admin provider or bridgeDashboard Filament widget resolverWidget hiddenAssert widget appears in expected dashboard slot.
Overview statCapellAdmin::registerOverviewStat(...)Admin providerDashboard statsStat hiddenAssert stat key appears for allowed user/context.
User menu itemCapellAdmin::registerUserMenuItem(...)Admin providerUser menu registryMenu item hiddenAssert user menu registry contains item.
Role workspace toolAdminBridgeRegistrar::workspace(AdminWorkspaceItemData $item)Admin bridge or runtime/admin providerHeader workspace switcherItem hiddenRegister a translated label, a typed Page/Resource URL with isAbsolute: false, and the required permission; assert the bridge definition and visible filtered result.
Admin header toolAdminToolItem::TAGTag class in providerAdmin tool registryTool hiddenResolve app()->tagged(AdminToolItem::TAG).
Page form fieldsPageSchemaExtender::TAGTag extender in provider or bridgePage schema resolverBase page form onlyBuild schema for fixture page and assert field exists.
Site form fieldsSiteSchemaExtender::TAGTag extender in provider or bridgeSite schema resolverBase site form onlyBuild schema for fixture site and assert field exists.
Layout form fieldsLayoutSchemaExtender::TAGTag extender in provider or bridgeLayout schema resolverBase layout form onlyBuild schema for fixture layout and assert field exists.
User resource contributionsUserResourceBridge::TAGRegister through admin bridgeUser resource bridge resolverBase user resource onlyAssert context, lifecycle, and visible contribution behavior.
Page table queryPageTableExtender::TAGTag extender in providerPage tables and page selectsBase queryAssert modified query includes expected relation/filter.
Page table publish/workflow statusPageTableStatusResolver container bindingAdmin providerPages table status columnPublish-date status resolverBind a fake resolver and assert the table renders its state.
Header actionsPageHeaderActionExtender::TAG, SiteHeaderActionExtender::TAG, ResourceHeaderActionExtender::TAGTag extender in providerResource/page action resolversAction hiddenRender header actions and assert action key.
Site row actionsSiteRecordActionExtender::TAGTag extender in providerSites tableAction hiddenRender table actions for fixture site.
Publish panel contentPublishPanelExtender::TAGTag extender in providerPage publish sectionPanel remains unchangedAssert view/html appears only for supported page.
Page edit contentPageEditExtender::TAGTag extender in providerEdit pageExtra content hiddenRender edit page and assert component appears.
Page resource pages/widgetsPageResourcePageExtender::TAG, PageResourceWidgetExtender::TAGTag extender in providerPage resourceExtra pages/widgets hiddenAssert PageResource includes class.
Page export fieldsPageExportExtender::TAGTag extender in providerPage/site export actionsExtra fields omittedRun export action and assert payload contains field.
Media edit actionsMediaEditActionExtender::TAGTag extender in providerMedia edit pageAction hiddenRender media edit actions.
Extensions page contentExtensionsPageExtender::TAG or AdminBridgeRegistrar::extensionsPageExtender()Admin bridge or providerInstalled Extensions pageNo package alert/contentRender Extensions page and assert package section.
Extensions page actionsAdminBridgeRegistrar::extensionsPageHeaderAction(), extensionsPageHeaderActionGroupAction(), or extensionsPageTableAction()Admin bridgeInstalled Extensions pageAction hiddenResolve registry and assert header/table action key.
Admin panel customizationAdminPanelExtender::TAGTag extender in providerCapellAdminPluginBase panel configBuild panel and assert plugin/middleware/theme change.
Validation gatesValidationSubscriber or subscriber contractsRuntime/admin providerSubscriber manager/admin eventsValidation not appliedNotify event with fixture and assert error/result.
Admin event handlersAdminEventRegistry::register(...)Admin providerHasDynamicEventListenersEvent has no extra handlerDispatch Livewire event and assert handler ran.
Activity resource links/display/revertCapellAdmin::registerActivityResourceLink(...), ActivityChangeSetBuilder::TAG, ActivityRevertHandler::TAGAdmin providerActivity table/revert actionsDefault activity behaviorRun activity link builder/revert fixture.
Site Health reportSiteHealthReportExtender::TAGAdmin providerSite Health action/pageReport section absentBuild Site Health report and assert section.

Workspace tool URLs are a discovery-only contract and must resolve to root-relative, same-origin paths. Use Page::getUrl(..., isAbsolute: false), Resource::getUrl(..., isAbsolute: false), or route(..., absolute: false) when those typed targets exist. The registry rejects absolute, protocol-relative, control-character, and backslash-containing destinations; direct Page/Resource policies remain authoritative.

NeedAPIRegister fromCalled bySafe fallbackTest recipe
Small public HTML injectionRenderHookRegistry::register(...)Frontend/runtime providerPublic Blade hook callsHook outputs nothingRender anonymous page and assert safe HTML.
Frontend component aliasFrontendComponentRegistryInterface::register(...)Frontend providerRuntime component resolverAlias is unavailableAssert has() and hasReference() for key/alias.
Builder block renderingLayoutWidgetRegistry::register(...) with frontend targetRuntime/frontend providerContent rendererBlock is skipped/unrenderableRender fixture block and assert output.
Reserved public pathReservedFrontendPathRegistry::reserveExact() / reservePrefix()Frontend provider before fallback routesFrontend route resolverPath falls through to page lookupAssert package route responds and page fallback does not catch it.
Public route middlewareFrontendRouteMiddlewareRegistryFrontend providerPublic page routeBase middleware orderAssert all() order or request side effect.
Frontend rule conditionFrontendRuleConditionRegistry::register(...)Frontend providerRuntime rulesRule never matchesDirectly evaluate condition against fixture context.
Response rendererFrontendResponseRendererRegistry::register() / registerClass()Frontend providerRuntime response pipelineDefault rendererResolve renderer for runtime and assert response class/content.
Cache invalidationCacheInvalidationRegistry::registerDependency(...)Frontend/runtime providerModel observers/cache invalidatorCache remains until TTL/manual clearSave fixture model and assert invalidation plan.
Runtime frontend assetsFrontendResourceRegistry groups / FrontendResourceContributor::TAGFrontend providerFrontend resource-plan builderAsset not emittedBuild resource plan and assert resource handle/url.
Runtime manifest contributorsFrontendRuntimeManifestContributor::TAGFrontend providerFrontend runtime builderManifest lacks package dataBuild runtime manifest and assert contribution.
Resource contributorsFrontendResourceContributor::TAGFrontend providerFrontend resource-plan builderAsset absentBuild resource plan and assert package handle.
NeedAPIRegister fromCalled bySafe fallbackTest recipe
Browser install guide patchPatch plus PatchRegistry::register(...)Installer provider registerPatches()Install guide page/actionPatch absentProbe/apply fixture host file and assert idempotence.
Installer default packagesCAPELL_SETUP_DEFAULT_PACKAGES plus package metadataHost env/configBrowser installer page dataPackage not preselectedBuild installer page data and assert default list.
Extension dashboard Filament widgetCapellAdmin::registerDashboardFilamentWidget($widget, DashboardEnum::Extensions) or $registrar->extensionDashboardFilamentWidget($widget)Admin provider or admin bridgeExtensions dashboardWidget absentRegister fixture widget and assert it appears in DashboardEnum::Extensions.
Marketplace installed extensions contentAdminBridgeRegistrar::extensionsPageExtender()Marketplace admin bridgeExtensions dashboard actions areaNo Marketplace alertRender Extensions page in connected/unconnected states.
Marketplace theme header actionAdminBridgeRegistrar::resourceHeaderActionExtender()Marketplace admin bridgeTheme resource headerAction hiddenRender theme resource action list.
Marketplace licensing and activationCapell\Core\Contracts\Marketplace\ExtensionEntitlements bindingMarketplace providerLicence decisions, runtime gateNullExtensionEntitlementsResolve the contract and verify a signed fixture receipt.

Alongside the runtime extension points above, a package declares what it contributes in its capell.json contributes[] array. Each entry names a type and a class, and the class must implement the contract that type maps to.

These contracts are marker interfaces. They declare no registration methods — the only method comes from the base contract, Capell\Core\Contracts\Extensions\ExtensionContribution:

public static function compatibleCapellApiVersion(): string;

The marker’s job is validation and traceability, not wiring. You still register runtime behaviour through the extension points documented above; the declaration is what lets manifest audits know the surface exists.

Official Core, Admin, Frontend, Installer, and Marketplace registrars emit a neutral ExtensionContributionReceiptData receipt containing the owning package, provider bucket, contribution type, stable key, implementation, and source class. The RecordsExtensionContributionReceipt is the public emission boundary. Package boot context supplies the owner and bucket; foundation-owned built-ins are marked explicitly.

Receipt emission is attached to the documented public registration boundaries, not only to convenience wrappers: CapellCore::registerPageType(), registerModels(), registerModelInterceptor(), and registerComponent(s); CapellAdmin::contributeToAdminSurface(); RenderHookRegistry registration methods; FrontendComponentRegistry::register(); FrontendAssetsService::registerAsset(); FrontendResourceRegistry::register(); FrontendResponseRendererRegistry::register() / registerClass(); CacheInvalidationRegistry::registerDependency(); TailwindAssetsRegistry registration methods; RenderableRegistry::register(); and the Core InstallPatchRegistry / Installer PatchRegistry seams. Marketplace and Installer Admin bridges use the shared AdminBridgeRegistrar, which supplies the capell-app/marketplace or capell-app/installer owner and the admin bucket from the selected package context. Tagged contributors are attributed from their supplying package context at resolution time; consumer registries do not manufacture a new owner.

Manifest contributions may include a stable key and providerBucket alongside their marker class. The existing contributionTraceability envelope retains deferred/runtime integration metadata. Audits reconcile receipts only for explicitly booted provider buckets, reporting declared-only, loaded-only, wrong-owner, or wrong-bucket states with package/key/source diagnostics.

namespace Vendor\Example\Routes;
use Capell\Core\Contracts\Extensions\RegistersExtensionRoute;
final class ExampleRoutes implements RegistersExtensionRoute
{
public static function compatibleCapellApiVersion(): string
{
return '^1.0';
}
}
capell.json
"contributes": [
{ "type": "route", "class": "Vendor\\Example\\Routes\\ExampleRoutes" },
{ "type": "content-widget", "key": "vendor.hero", "class": "Vendor\\Example\\Widgets\\HeroWidget" }
]
typeContract (in Capell\Core\Contracts\Extensions)
admin-resourceRegistersExtensionAdminResource
sectionRegistersExtensionSection
page-type, page-variationRegistersExtensionPageType
dashboard-widget, overview-statRegistersExtensionFilamentWidget
permissionRegistersExtensionPermission
routeRegistersExtensionRoute
settingRegistersExtensionSetting
frontend-componentRegistersExtensionFrontendComponent
content-widgetRegistersExtensionContentWidget
render-hookRegistersExtensionRenderHook
public-render-dataRegistersExtensionPublicRenderData
assetRegistersExtensionAsset
migrationRunsExtensionMigration
scheduled-jobRunsScheduledExtensionJob
health-checkChecksExtensionHealth
content-graphContentGraphExtractor
workflow-attentionContributesWorkflowAttention

Validation rejects a manifest when the class sits outside the package’s own PSR-4 namespace, spoofs a Capell namespace, or does not implement the mapped contract. A content-widget also needs a key matching ^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9.-]*$, prefixed with your vendor and unique within the manifest. Content widgets were added in API 1.1, so declare ^1.1 for those.

Verify with php artisan capell:extension-audit, explore with php artisan capell:extension-playground, and assert in tests with Capell\Core\Testing\ExtensionTestHarness::assertContributionRegistered(). For host conformance tests, ExtensionTestHarness::bootProviders($app, $role) runs the same package provider loader used during application bootstrap and returns the providers that actually registered. This is the Core-owned seam for checking provider-role isolation, receipt context, and registration order; the positive package example remains the capell-app/extension-cookbook showcase.

An application adding its own frontend route must apply Capell’s middleware stack, or there is no resolved site, page, or language context. Take the canonical list from Capell\Frontend\Support\Routing\FrontendRouteMiddlewareRegistry::all() rather than hand-listing aliases — it is the source of truth and supports prepend(), append(), insertBefore(), and insertAfter().

AliasPurpose
frontend.resolveBootstraps the frontend kernel and resolves site, page, and language. Required.
frontend.maintenanceServes cached maintenance and lockdown pages. Lockdown ignores all bypasses.
frontend.anonymous_cacheable_renderNulls the user resolver so an authenticated response cannot poison a shared HTML cache entry
frontend.rendering_strategyAdds an X-Rendering-Strategy diagnostic header. Optional, not in the default stack.
frontend.etag, frontend.asset-optimizationETag and asset optimisation. Optional, not in the default stack.

capell-frontend.blade_components and capell-frontend.livewire_components are flat name => class maps and the supported way for a theme or app to register or override a public component. Entries whose value is not a string — or, for Livewire, not a Livewire\Component subclass — are silently ignored.

Packages should contribute through Capell\Frontend\Contracts\FrontendComponentContributor instead, tagged with that interface’s TAG constant. Contributor entries take precedence over config.

Three contracts exist in both Core and a downstream package. In each case Core owns the definition and the downstream copy is a deprecated, empty extending alias. Existing implementations of a downstream alias remain valid implementations of the Core contract, but new code must import and bind the Core contract. Migrate existing imports before the downstream aliases are removed in the next major release.

Deprecated aliasReplace withNote
Capell\Frontend\Contracts\RedirectResolverCapell\Core\Contracts\RedirectResolverPublic page resolution reads the Core contract. Binding only the deprecated alias does not change redirect behaviour.
Capell\Admin\Contracts\SettingsSchemaContractCapell\Core\Contracts\SettingsSchemaContractSettings discovery accepts the Core contract.
Capell\Admin\Contracts\Themes\ThemePreviewRendererInterfaceCapell\Core\Contracts\Themes\ThemePreviewRendererInterfaceThe frontend renderer is bound to the Core contract.
  • Register in the package that owns the behavior.
  • Return null, an empty array, or no-op output when the package does not support the current context.
  • Test both the expected contribution and the safe fallback.
  • Public frontend extensions must pass public HTML safety.
  • The HTML sanitizer is rebuilt from capell-frontend.html_content_allowed_attributes for each invocation. Site-scoped configuration resolved before rendering therefore remains isolated between requests, including in long-lived workers.