Plugin registries and resolvers
Full PluginRegistrar fluent surface grouped by domain — exact signatures, manifest gates, examples, and links to registry contract pages.
Scoped registrar
When PluginServiceProvider boots an enabled plugin it calls:
public function forPlugin(PluginManifest $manifest, string $package): self
Scoped instances enforce wiring ⊆ declaration for permissions, settings keys, hook names, and slot+component pairs. Unscoped instances (core / tests) apply no manifest gating.
public function isScoped(): bool
public function package(): ?string
public function manifest(): ?PluginManifest
Source: app/Services/Plugins/PluginRegistrar.php.
Auth & permissions
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
permission |
permission(string $key, array $definition): self |
key ∈ permissions[] |
Permission Registry |
ability |
ability(string $key, array $definition): self |
no | Ability Registry |
$registrar->permission('signals.xero.sync', [
'label' => 'Sync with Xero',
'description' => 'Push accounts and invoices to Xero.',
'group' => 'Integrations',
]);
Settings
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
setting |
setting(SettingsDefinition $definition): self |
every defaults() key ∈ settings[].key |
Settings Registry |
Prefer manifest → PluginSettingsManager for plugin-private config — see Plugin settings.
Events & hooks
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
webhookEvent |
webhookEvent(string $name): self |
name ∈ events[] with webhook: true |
Webhook Event Registry / Event Registry |
event |
event(string $name, array $visibility = []): self |
name ∈ events[]; webhook: true requires manifest webhook: true |
Event Registry |
notification |
notification(string $key, array $definition): self |
key ∈ notifications[] |
Notification Registry |
hook |
hook(string $name, HookType $type, callable $handler, int $priority = 50): self |
name ∈ hooks[]; Event/Filter must exist in EventRegistry |
Plugin Hook Registry |
$registrar->hook('account.updated', HookType::Event, $contactPusher, 50);
Guide: Plugin hooks, Plugin events.
UI slots & navigation
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
slot |
slot(string $slot, SlotComponentType $type, string $component, ?callable $data = null, int $priority = 50, ?string $permission = null): self |
slot+component pair ∈ slots[] |
Slot Registry |
navItem |
navItem(array|NavigationItem $item): self |
no | NavigationService (no dedicated contract page) |
navGroup |
navGroup(string $label, string $icon, array $items): self |
label/icon must match nav; every item must match a declared nav.items[] entry |
PluginNavRegistry (no dedicated contract page) |
page |
page(string $key, callable $dataProvider): self |
requires a plugin-scoped registrar; key ∈ pages[] |
PluginNavRegistry (no dedicated contract page) |
paletteCommands |
paletteCommands(callable $provider): self |
no manifest section; each command's permission must be null or ∈ permissions[] |
PluginPaletteRegistry (no dedicated contract page) |
datatable |
datatable(string $key, array $definition): self |
key ∈ datatables[]; a declared permission binds the definition to that exact key, and a permission is mandatory either way |
Plugin Data Table Registry |
datatableSlot |
datatableSlot(string $slot, string $datatableKey, int $priority = 0, ?string $permission = null): self |
the slot must be declared in slots[] with component components.data-table; the datatable must already be registered |
Slot Registry · Plugin Data Table Registry |
| column | column(string $entityType, array $config): self | no manifest section; the column key must carry the vendor prefix | Plugin Column Registry |
$registrar->datatable('acme.fieldops.jobs', [
'model' => Job::class,
'permission' => 'acme.fieldops.view',
'searchable' => ['reference', 'title'],
'columns' => [['key' => 'reference', 'label' => 'Job', 'sortable' => true]],
]);
$registrar->datatableSlot('dashboard.widgets', 'acme.fieldops.jobs', priority: 80);
column() is the other half: instead of a whole plugin-owned table, it adds one column to a core entity's list views. The column is merged into that entity's ColumnRegistry when it boots, so it reaches DataTable rendering, the column picker, live filters and CSV export exactly as a core column does. Keys are public (saved live filters store them), so a plugin-scoped registration must carry the vendor prefix — which is what keeps a plugin from shadowing a core column.
$registrar->column('rentals', [
'key' => 'acme.crew_hours',
'label' => 'Crew Hours',
'field' => 'crew_hours',
'sortable' => true,
'filterable' => true,
]);
datatable() is config only — it resolves into mount params for the one canonical components.data-table Livewire component every core listing page uses, so plugin tables inherit search, sorting, filtering, column toggles, live filters, and CSV export. Render it from a datatable page section or through datatableSlot().
Guide: Plugin UI, DataTable SDK.
Documents
None of these are manifest-gated.
| Method | Signature | Contract |
|---|---|---|
documentType |
documentType(string $documentType, string $resolverType, string $modelType, string $label, string $description = '', string $permission = 'documents.create', ?string $category = null, array $aliases = []): self |
Document Type Registry |
documentResolver |
documentResolver(DocumentDataResolver $resolver): self |
Document Resolver Registry |
extendDocumentResolver |
extendDocumentResolver(string $resolverType, callable $extender): self — @param callable(array<string, mixed>, Model): array<string, mixed> |
Document Resolver Registry |
pdfDriver |
pdfDriver(string $name, string $driverClass): self — @param class-string<PdfDriver> |
PDF Driver Manager |
seedDocumentTemplates |
seedDocumentTemplates(callable $seeder): self — @param callable(Warehouse): void |
Plugin Document Template Seeder Registry |
paymentDriver |
paymentDriver(string $name, string $driverClass): self — @param class-string<PortalPaymentDriver> |
PortalPaymentDriverManager (no dedicated contract page) — hosted-checkout providers for the Document Portal pay rail |
Guide: Plugin documents.
Notifications
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
channelDriver |
channelDriver(string $key, string $driverClass): self |
yes (channel_drivers[]) |
Channel Provider Registry · Plugin channel providers |
recipientResolver |
recipientResolver(RecipientResolver $resolver): self |
no | Recipient Resolver Registry |
deliveryTrackingResolver |
deliveryTrackingResolver(DeliveryTrackingResolver $resolver): self |
no | Delivery Tracking Resolver Registry |
registerFilter |
registerFilter(string $name, callable $filter): self |
no | Merge Field Filter Registry |
Pricing & rates
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
rateStrategy |
rateStrategy(CalculationStrategy $strategy): self |
no | Rate Engine Registry |
rateModifier |
rateModifier(RateModifier $modifier): self |
no | Rate Engine Registry |
discountPredicate |
discountPredicate(DiscountCriteriaPredicate $predicate): self |
no | Discount Criteria Predicate Registry |
discountSource |
discountSource(DiscountSource $source): self |
no | Discount Source Registry |
dealPriceValidator |
dealPriceValidator(DealPriceValidator $validator): self |
no | Deal Price Validator Registry |
Availability & shortages
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
demandSource |
demandSource(DemandSourceDefinition $definition): self |
no | Demand Source Registry |
shortageResolver |
shortageResolver(ShortageResolverDefinition $definition): self |
no | Shortage Resolver Registry |
costApportionment |
costApportionment(CostApportionmentStrategyContract $strategy): self |
no | Cost Apportionment Registry |
Rental guards
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
opportunityValidator |
opportunityValidator(TransitionRule $validator): self |
no | Plugin Validator Registry |
Diagnostics
| Method | Signature | Manifest gate | Contract |
|---|---|---|---|
doctorCheck |
doctorCheck(string $diagnostic): self |
no | Laravel\Doctor\Diagnostic |
Adds a check to php artisan doctor, reported alongside the core Signals and Laravel diagnostics. Use it for the things only your plugin can verify — an integration's credentials, a partner endpoint that answers, a webhook secret that is still valid.
$registrar->doctorCheck(AcmeIntegrationIsConfigured::class);
Scaffold a diagnostic with php artisan make:diagnostic. Registration records the class only; it is not instantiated until a diagnostic run selects it, so nothing runs during the plugin lifecycle. Passing a class that does not extend Diagnostic throws PluginRegistrarException.
See System Health for the command and the core diagnostics.
Flightcase lifecycle listeners
These register Laravel Event::listen handlers. Manifest gate: matching hook name must be declared in hooks[].
public function onFlightcaseBeforePack(callable $listener): self
public function onFlightcaseAfterPack(callable $listener): self
public function onFlightcaseBeforeSeal(callable $listener): self
public function onFlightcaseAfterSeal(callable $listener): self
public function onFlightcaseBeforeDissolve(callable $listener): self
public function onFlightcaseAfterDissolve(callable $listener): self
public function onFlightcaseBeforeDispatch(callable $listener): self
public function onFlightcaseAfterDispatch(callable $listener): self
public function onFlightcaseBeforeRepack(callable $listener): self
public function onFlightcaseAfterRepack(callable $listener): self
public function onFlightcaseBeforeTransfer(callable $listener): self
public function onFlightcaseAfterTransfer(callable $listener): self
Import / export
Shipped with the import/export engine merge — real signatures from PluginRegistrar:
| Method | Registry | Contract |
|---|---|---|
| `importTransform(Transform | TransformContract $transform)` | TransformRegistry |
importableModel(ImportableModelContract $model) |
ImportTargetRegistry |
plugin import targets (+ optional SupportsPolymorphicImport) |
exportableModel(ExportableModelContract $model) |
PluginExportRegistry |
plugin export sources (extends PluginExportHandler) |
| `planTemplate(ImportPlanTemplateDefinition | PlanTemplateContract $template)` | ImportPlanTemplateRegistry |
Walkthroughs: Plugin import/export and External DB sync plugin.
Field-level importable / exportable flags already exist on FieldDefinition / SchemaRegistry — that is not the same as registrar import/export seams.
Gate exception messages (examples)
Plugin [{$package}] attempted to register undeclared permission [{$key}].Plugin [{$package}] attempted to register undeclared setting [{$key}].Plugin [{$package}] attempted to register undeclared hook [{$name}].Plugin [{$package}] attempted to register undeclared slot [{$slot}] component [{$component}].Plugin [{$package}] attempted to register unknown hook [{$name}] of type [{$type->value}]; name must exist in EventRegistry.