GatewayAdapterInterface) or themes (which override checkout templates), addons extend the platform through event hooks, custom HTTP routes, cron jobs, admin menu entries, and database-backed functionality.
How addon plugins work
PluginLoader::loadActive() scans modules/addons/, validates each manifest.json, token-scans all PHP files, and calls the two-phase lifecycle:
register(EventManager $events, Container $container)- called during boot phase. All hook and filter registrations are attributed to the plugin slug.boot(Container $container)- called after all plugins have registered. Use this phase to capture container services.
Directory structure
The manifest.json file
The entrypoint class
The entrypoint must implementOwnPay\Plugin\PluginInterface:
Accessing core services
Plugins are not wired into the DI container automatically. Resolve services manually from the$container argument:
Routes
Routes declared inmanifest.json under the routes array are registered into the core router:
Cron jobs
Declare scheduled jobs inmanifest.json under the cron array:
every_minute, every_5min, every_15min, every_30min, hourly, every_6hours, daily, weekly.
Admin menu entries
Database migrations
Create amigrations/ directory with .sql files. Plugin tables must be prefixed op_plugin_.
Security requirements
- No
eval()or OS commands - No direct
op_*table access - Escape all output
- Use parameterized queries
- Respect tenant isolation
Checklist
manifest.jsoncomplete;slugmatches directory name- Entrypoint implements
PluginInterfacewith all six methods - All hooks registered inside
register() uninstall()drops anyop_plugin_tables- Plugin tables named
op_plugin_{slug}_* - Admin routes use the
"admin"middleware group - Cron jobs implement
CronJobInterface - No
eval(),exec(),shell_exec()