Appearance
How does a plugin get installed?
A mental model of what happens between erp plugin publish and your page being live. Knowing this makes install errors readable.
Build
erp plugin build spk-assembly -o x.spk zips spk-assembly/ into a .spk file. Nothing clever — it's a zip with plugin.json at the root, metadata/, and lib/. It runs erp plugin validate first and refuses to package invalid pages.
Upload
erp plugin publish x.spk --tenant N POSTs the file to /api/v1/authoring/plugins/upload with your erp login token. The server:
- recomputes the checksum and compares — it never trusts the client digest.
- stores the artifact at
system/plugins/<id>/<version>/<id>-<version>.spk. This key is immutable — re-storing different bytes under the same version is refused. - reads the manifest —
idandversionmust be present and non-blank.mainClassis optional: a pure-JSON plugin leaves itnulland the platform loads it via a built-in generic no-op entry point.
Install pipeline (PluginInstallPipeline.installNew)
In order, all-or-nothing:
| Stage | What it does | Common failure |
|---|---|---|
| VALIDATING | schema + block-registry validation; artifact persistence | immutable-artifact conflict; a page block property that doesn't exist |
| LOADING | PF4J loads lib/*.jar (or the built-in entry point for a pure-JSON plugin), calls the plugin's start() | a declared mainClass not found in the jar; another version of the same plugin id already loaded |
| entities | create / ALTER TABLE ADD COLUMN (idempotent-additive — never drops) | label not valid JSON; required without nullable |
| providers / views / services / pages / menus | upsert each artifact | route/module mismatch |
| rules / workflows | install | stagesJson authored as an object not a string |
| i18n / roles | install | role name or code collides with another installed plugin |
| seed data | PluginDataSeedInstaller upserts each metadata/seed-data/*.json by keyFields | entity not shipped; non-unique key |
| register rules fire | AFTER_CREATE rules on config entities call ensure<Job>Registered → your scheduled jobs get an erp_job row | (seed no config row → job never registers) |
If any stage throws, everything rolls back, including the PF4J load. The error names the stage:
plugin install failed at stage LOADING: ...
plugin install failed after code load, rolled back PF4J load: Role "..." already existsUpgrade
Publishing a higher version while the plugin is installed is an upgrade. The platform's convergence loop unloads the old PF4J code, loads the new, and re-runs the artifact/seed stages (all upserts, nothing dropped).
Uninstall (and why not to, mid-development)
DELETE /api/v1/authoring/plugins/<id> marks the installation removed. The code is unloaded asynchronously by a background convergence loop, not immediately. If that unload stalls (it can, on a transient DB error), the old PF4J code stays loaded, and the next publish — which uses the install-new path — is refused with "already loaded plugin ... same id". Recovery is a backend restart or a new plugin id.
Practical rule: to iterate, bump the version and re-publish. Don't uninstall.
Verify an install
bash
erp plugin list # your plugin: state "installed", pf4jState "STARTED"
erp api get "/api/v1/authoring/pages?module=<yourModule>" # pages registered