Skip to content

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:

  1. recomputes the checksum and compares — it never trusts the client digest.
  2. 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.
  3. reads the manifestid and version must be present and non-blank. mainClass is optional: a pure-JSON plugin leaves it null and the platform loads it via a built-in generic no-op entry point.

Install pipeline (PluginInstallPipeline.installNew)

In order, all-or-nothing:

StageWhat it doesCommon failure
VALIDATINGschema + block-registry validation; artifact persistenceimmutable-artifact conflict; a page block property that doesn't exist
LOADINGPF4J 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
entitiescreate / ALTER TABLE ADD COLUMN (idempotent-additive — never drops)label not valid JSON; required without nullable
providers / views / services / pages / menusupsert each artifactroute/module mismatch
rules / workflowsinstallstagesJson authored as an object not a string
i18n / rolesinstallrole name or code collides with another installed plugin
seed dataPluginDataSeedInstaller upserts each metadata/seed-data/*.json by keyFieldsentity not shipped; non-unique key
register rules fireAFTER_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 exists

Upgrade

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