SIGNALS Documentation
API Reference

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.