SIGNALS Documentation
API Reference

Plugin getting started

Build and enable a minimal local Signals plugin in about ten minutes — folder layout, composer.json, signals.yaml, PluginBase, and CLI lifecycle.

Prerequisites

  • A working Signals install on this branch (Composer deps, migrations, Vite not required for CLI checks)
  • PHP 8.5 CLI available as php
  • Permission to create directories under plugins/

You do not need to publish a Composer package. Local discovery scans plugins/{name}/ and vendor-nested plugins/{vendor}/{name}/ (see Plugin folder structure).

1. Create the package directory

mkdir -p plugins/acme/hello/{src,database/migrations}

2. composer.json

{
    "name": "acme/hello",
    "description": "Minimal Signals plugin quickstart",
    "type": "library",
    "autoload": {
        "psr-4": {
            "Acme\\Hello\\": "src/"
        }
    },
    "extra": {
        "signals": {
            "plugin": "Acme\\Hello\\HelloPlugin"
        }
    }
}

Discovery requires:

  • Composer name matching signals.yaml package
  • Non-empty extra.signals.plugin FQCN that extends App\Sdk\PluginBase
  • Package-root signals.yaml

Local packages are loaded on demand by PluginClassLoader from the package’s autoload.psr-4 map — you do not need to require the package from the application composer.json.

3. Minimal signals.yaml

package: acme/hello
name: Acme Hello
version: 0.1.0
signals_version: "^1.0"

permissions:
  - acme.hello.view

hooks: []

slots:
  - slot: dashboard.widgets
    component: signals.stat-card
    priority: 90
    permission: acme.hello.view

settings:
  - key: greeting
    type: string
    label: Greeting
    default: Hello from Acme
    rules:
      - nullable
      - string
      - max:255

network: []

Permissions must use the Composer vendor prefix (acme.). Full rules: Plugin manifest.

When registering permissions in PHP, you may pass optional capabilities tags (money, operations, crm, admin, reporting, destructive) so the role builder wizard can place them under the right high-level toggles. Tag destructive grants explicitly.

Check your manifest as you type with the Manifest validator & builder — it runs the same ManifestValidator the install flow uses, and can generate this whole file from a form.

4. Plugin entry class

plugins/acme/hello/src/HelloPlugin.php:

<?php

namespace Acme\Hello;

use App\Sdk\PluginBase;
use App\Sdk\Signals;
use App\Sdk\Slots\SlotComponentType;
use App\Services\Plugins\PluginRegistrar;

class HelloPlugin extends PluginBase
{
    public function register(PluginRegistrar $registrar): void
    {
        $registrar->permission('acme.hello.view', [
            'label' => 'View Acme Hello',
            'description' => 'See the Acme Hello dashboard widget.',
            'group' => 'Integrations',
            'capabilities' => ['admin'],
        ]);

        $package = $this->package;

        $registrar->slot(
            'dashboard.widgets',
            SlotComponentType::View,
            'signals.stat-card',
            static function (array $context) use ($package): array {
                return [
                    'label' => 'Acme Hello',
                    'value' => (string) Signals::setting($package, 'greeting', 'Hello'),
                    'color' => 'blue',
                    'hint' => 'Quickstart plugin',
                ];
            },
            90,
            'acme.hello.view',
        );
    }
}

Optional lifecycle hooks on PluginBase: install(), enable(), disable(), uninstall(), onUpdate(), boot().

5. Validate, install, enable

php artisan signals:plugin list
php artisan signals:plugin check acme/hello
php artisan signals:plugin install acme/hello
php artisan signals:plugin enable acme/hello

CLI actions (from PluginCommand): list, install, remove, enable, disable, check.

check runs manifest validation. install creates the plugins row, runs guarded migrations (none in this minimal package), seeds permissions and settings defaults, then calls PluginBase::install(). enable sets status enabled and calls enable().

Enable takes effect on the next application boot. PluginLifecycleManager::enable() does not hot-register hooks or slots. Restart PHP-FPM / Octane workers (or the next Herd request cycle after a process recycle) before expecting the dashboard widget.

6. Verify in admin + dashboard

  1. Open Admin → Settings → Plugins (/admin/settings/plugins, route admin.settings.plugins).
  2. Confirm acme/hello is listed as enabled; expand for overview / permissions / slots / settings.
  3. Open the dashboard. With a user that can acme.hello.view, the dashboard.widgets slot should render an signals.stat-card labelled Acme Hello.

Edit the greeting under the plugin’s generated settings form, save, reload the dashboard after the next boot if you also toggled enable state.

7. Iterate

Typical loop:

  1. Edit signals.yaml and/or HelloPlugin::register().
  2. php artisan signals:plugin check acme/hello
  3. Reload the app process (required after enable/disable; usually enough after code edits in local PHP-FPM).
  4. Re-test the slot / settings.

When you add tables, declare them under tables: as plugin_* names and put migrations in database/migrations/ — the lifecycle guard rolls back any batch that creates a non-plugin_ table. See Plugin folder structure.

Next reading

Guide Why
Folder structure Canonical tree and Xero layout
Manifest reference Every YAML section and validation message — including nav, pages[], notifications[], events[]
UI slots & navigation Slot types, live slot names, modals, nav groups + pages, palette, recents, notifications
Plugin events Provided events, Signals::emit(), self-subscription
LLM / agent guide Copy-paste agent instructions
Examples signals/slots-demo kitchen-sink reference, signals/xero-sync, acme-example fixture