SIGNALS Documentation
API Reference

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.