Plugin Development Guide
This guide is for developers who want to add a feature to Xilancer without editing core. It covers the framework contract, every hook and slot, plugin-owned MVC, security, testing, packaging, and migration from the old module layout.
The reference implementation is the framework version 1.0.0 shipped with Xilancer 4.3.0.
Read in this order
- Overview - the three layers, the invariants, and what a plugin can do.
- Creating a Plugin - scaffold a working plugin in ten minutes.
- Manifest Reference - every field in
plugin.json, with validation rules. - Lifecycle & Discovery - discovery, dependency ordering, validation, activation, boot, and failure isolation.
- Hooks, Slots & Registry - the full contract: filters, actions, slots, registry domains, and payload shapes.
- Routes, Views & Settings - plugin-owned MVC, storage, settings, and admin menus.
- Security & Best Practices - escaping, capabilities, secrets, SSRF, idempotency, and performance.
- Testing & Packaging - hermetic tests, the validator, zip layout, and release checks.
- Plugin Catalog - every shipped plugin, its hooks, tables, and permissions.
- Legacy Module Migration - move a
module.jsonmodule intoplugin.jsonwithout breaking old views or config.
The one-paragraph version
Core calls guarded plugin_*() helpers at seam points. The framework dispatches filters, actions,
slots, and registry lookups to active plugins. A plugin is a directory with a plugin.json manifest
and an entry class that implements PluginInterface and receives a PluginContext. Plugins are
inactive by default; activation is stored outside core. A plugin never imports App\* or
Modules\*; if it needs core data, core passes it through a seam.
Conventions used in this guide
core/is the Laravel application.plugins/is the plugin root, a sibling ofcore/.- Code marked core-side is something the Xilancer team adds; plugin authors cannot add new seams from a plugin.
- Code marked plugin-side is what you write.
- All payloads are scalars and arrays. Models, requests, passwords, OTPs, and tokens are never passed.
What you cannot do
The framework is deliberately narrow. You cannot:
- Add a new core seam from a plugin. Ask for one, or use an existing filter, action, slot, or registry.
- Hook outbound mail. It has no central funnel.
- Bypass validation with a
*.create_datafilter. Validation runs first; the filter enriches. - Change ownership, statuses, passwords, or flags through a data filter. The allow-list is re-asserted after your filter runs.
- Hook subscription expiry. It is passive and has no transition.
- Assume a model instance in a payload. You receive IDs and scalars.
Getting help
- Read the Plugin Catalog for a plugin close to your feature.
- Copy
plugins/example-hello-worldfor the smallest working example. - Read
plugins/HOOKS.mdin the repository for the file-and-line host of every seam. - Run
php core/artisan plugins listafter every change.
Last updated: October 21, 2026

