Plugin SDK
Working Plugin SDK guide for Signals extensions — App\Sdk stability boundary, lifecycle overview, and links to dedicated plugin guides.
Overview & architecture
Signals plugins are Composer packages that extend the platform without modifying core code, core tables, or core migrations. Every plugin declares a static signals.yaml manifest. The framework validates that declaration before any plugin code runs, then mediates data access, HTTP, storage, and UI injection through the App\Sdk stability boundary.
Two reference implementations ship with the framework: signals/slots-demo (plugins/signals/slots-demo) — the kitchen-sink example wiring every SDK surface — and signals/xero-sync (plugins/signals/xero-sync) for HTTP + custom-field sync work.
Lifecycle
discover → validate → install → enable → boot → disable → uninstall
| Phase | What runs |
|---|---|
| Discover | PluginDiscovery scans Composer installed.json (extra.signals.plugin) and local packages under plugins/ — including vendor-nested plugins/{vendor}/{name}/. |
| Validate | PluginManifest::fromYamlFile() parses YAML and runs ManifestValidator. |
| Install | PluginLifecycleManager::install() creates the plugins row, runs guarded migrations, seeds permissions + settings defaults + notification types, calls PluginBase::install(). Status → installed. |
| Enable | Marks status enabled and calls PluginBase::enable(). Takes effect on the next application boot — the manager does not hot-boot. |
| Boot | PluginServiceProvider loads enabled packages (local PSR-4 via PluginClassLoader), scopes a PluginRegistrar, calls register() then boot(), then bridges Laravel string events and AuditableEvent → plugin Event hooks. |
| Disable | Soft-disable: status disabled, PluginBase::disable(). Migrations, data, and permissions are retained. |
| Uninstall | PluginBase::uninstall(), remove settings + permissions, roll back plugin migrations, delete the row. |
Stability boundary
Plugin packages depend on App\Sdk\* (contracts, manifest DTOs, Signals facade, PluginBase, hooks, slots, HTTP, storage, settings, data access). Runtime wiring lives in App\Services\Plugins\PluginRegistrar and App\Providers\PluginServiceProvider. Treat App\Sdk as the public surface; do not reach past it into core Eloquent models from plugin code except through the guarded facades.
No plugin-shipped views
Plugins never ship Blade views. UI extension is config-driven: declare a slot in the manifest, then register a core Blade/Livewire component name plus a data callable (or trusted HTML) via PluginRegistrar::slot().
Plugin guides
| Guide | Topic |
|---|---|
| Getting started | 10-minute local plugin quickstart |
| Folder structure | Canonical package tree |
| Manifest reference | Complete signals.yaml + validation messages |
| DataTable SDK | Shared DataTable API, column config schema, datatable() / datatableSlot(), live demo |
| Hooks | HookType, PluginContext, bridges, failure isolation |
| Events | EventRegistry, visibility, registering names |
| Registries & resolvers | Full PluginRegistrar surface |
| Settings | Manifest settings, encryption, Signals::setting() |
| Storage & HTTP | PluginStorage + PluginHttpClient |
| Data access | Guarded read / update / operation |
| Data models | Entity catalogue + field reference |
| UI | Slots, modals, nav groups + pages, palette, recents, notifications, admin form |
| Channel providers | Slack/Teams-style ChannelDriver plugins, channel_drivers manifest, lifecycle |
| Documents | Document type / resolver / PDF / template seams |
| Import / export | Merge status + interim patterns |
| LLM / agent guide | Copy-paste agent instructions |
| Examples | slots-demo kitchen sink + Xero Sync + Slack webhook + acme-example fixture |
| Coming soon | Deferred / aspirational roadmap only |
CLI: php artisan signals:plugin {list\|check\|install\|enable\|disable\|remove} [package]. Admin: /admin/settings/plugins.
Package anatomy
Canonical layout (detail: Folder structure):
plugins/signals/xero-sync/
├── composer.json # PSR-4 + extra.signals.plugin
├── signals.yaml # validated manifest
├── database/migrations/ # plugin_*-prefixed tables only
└── src/
├── XeroSyncPlugin.php # extends PluginBase
├── XeroClient.php
├── XeroContactPusher.php
├── XeroInvoicePusher.php
└── Models/XeroSyncLog.php
The current extension surface is registry-based. A registry is a framework service that stores definitions, drivers, validators, resolvers, or metadata for one domain area. Some of these registries already have core consumers and tests; some are empty seams designed for extension.
Local discovery supports both plugins/{name}/ and vendor-nested plugins/{vendor}/{name}/. Optional lifecycle hooks on PluginBase: install(), enable(), disable(), uninstall(), onUpdate(), boot().
Known limitations
| Limitation | Detail |
|---|---|
| Hot-boot | Enable/disable does not register hooks/slots until the next application boot. |
| Settings dual path | Manifest + PluginSettingsManager vs registrar->setting(SettingsDefinition) — see Settings. |
| Query builder | No programmatic list/filter API on the data facade; listing UI is covered by DataTable SDK — see Coming soon. |
| Local discovery depth | One vendor nesting level under plugins/ (plugins/{vendor}/{name}). |
| Operation allowlist | Only registry-mapped actions; Gate still applies inside the action. |
| Filter / Validator / Decorator call-sites | Event + AuditableEvent bridges shipped; other hook types await core wiring — see Coming soon. |
Registry contract reference
Each EXPOSE-v1 registry seam has a dedicated contract page describing identity, accepted input, collision rules, ordering, and shipped core registrations — authoritative for registry behaviour:
These EXPOSE-v1 registry contract pages are implemented and routable today (the Registry Contracts group in the Development sidebar is the authoritative list):
| Area | Contract page | Current purpose |
|---|---|---|
| API abilities | Ability Registry | API token ability discovery metadata |
| Permissions | Permission Registry | Administrative permission metadata and grouping |
| Settings | Settings Registry | Settings groups, defaults, validation rules, and type metadata |
| Webhooks | Webhook Event Registry | Outbound webhook event discovery metadata and metering weights |
| Domain events | Event Registry | Canonical domain-event catalogue and consumer visibility flags |
| Shortage costing | Cost Apportionment Registry | Virtual-stock cost allocation strategies |
| Deal pricing | Deal Price Validator Registry | Validators before proportional deal-price distribution |
| Notifications | Delivery Tracking Resolver Registry | Inbound delivery-tracking payload resolvers |
| Availability | Demand Source Registry | Availability demand definitions and resolvers |
| Discounts | Discount Criteria Predicate Registry | Discount criteria predicates |
| Discounts | Discount Source Registry | Runtime discount sources for rental totals |
| Documents | Document Resolver Registry | Document data resolvers and resolve extenders |
| Documents | Document Type Registry | Document type metadata, aliases, and source-model mapping |
| Documents | Plugin Document Template Seeder Registry | Warehouse-scoped document-template seeder callbacks |
| Plugin SDK | Plugin Hook Registry | Plugin hook handler registrations by name, type, and priority |
| Plugin SDK | Plugin Operation Registry | Operation name → core action class mapping |
| Plugin SDK | Plugin Tool Registry | Plugin MCP/CLI tool definitions (PluginRegistrar::tool) |
| Plugin SDK | Plugin Data Table Registry | Plugin listing-table definitions (PluginRegistrar::datatable) |
| Documents | PDF Driver Manager | Document PDF driver registration and resolution |
| Notifications | Channel Provider Registry | Notification delivery drivers |
| Notifications | Merge Field Filter Registry | Merge-field rendering filters |
| Pricing | Rate Engine Registry | Calculation strategies and rate modifiers |
| Rental workflow | Plugin Validator Registry | Transition-rule validators in the rental guard pipeline |
| Plugin SDK | Slot Registry | Plugin UI slot injections with priority and permission gating |
| Shortages | Shortage Resolver Registry | Shortage resolver definitions and implementations |
| Notifications | Recipient Resolver Registry | Notification recipient-rule resolvers |
These links use the public docs routes. The nested Markdown source path under docs/development/registries is an implementation detail.