Plugins
Distributing a Plugin
Packaging
A plugin is distributed as a .zip file, installed from Admin → Extensions via the upload form. The ZIP must contain plugin.json at its root, or inside a single top-level folder — both are supported:
my-plugin.zip
└── plugin.json
└── my-plugin-init.php
└── ...
Exclude anything generated at runtime:
data/— created automatically on first useprivate/— created automatically, contains secrets that should never ship pre-populated.DS_Storeand other OS/editor artifacts
Uploading a ZIP extracts the plugin but does not activate it — activation is always a separate, explicit step, so whoever installs it has a chance to configure it before it starts handling real requests.
Checklist for a new plugin
[ ] plugin.json at the plugin's root, with synaptik_plugin: true and a stable slug
[ ] Entry file loads cleanly when activated — no fatal errors,
no assumptions about which script triggered the load (admin vs front-end vs CLI)
[ ] Admin sidebar entry registered via pl_on_admin_menu(), if the plugin has an admin UI
[ ] Admin page rendered via {slug}_render_admin_page(), not a standalone layout
[ ] early_request / after_routing hooks used (not a core-file patch) if the
plugin needs to intercept a front-end request before rendering starts
[ ] Own data/ and private/ directories, created on demand with .htaccess protection
(always rewrite .htaccess — never skip with file_exists())
[ ] No writes to the CMS core's /data/ or /admin/ directories
[ ] CSRF protection on every state-changing action
[ ] Session reuse ($_SESSION['admin']) rather than a separate login system
[ ] Translations for en/fr/es, front-end and admin, following active_language / admin_language
[ ] All URLs built as absolute paths derived from the plugin's own filesystem
position — never hardcoded, never relative to the current script
[ ] data/ and private/ excluded from the distributed ZIP
