Appearance
Publishing and install errors
Every error message erp plugin publish can return, what it means, and what to do. The install pipeline is all-or-nothing and names the stage it failed at — see How does a plugin get installed?.
Before upload
plugin.json is missing mainClass (no longer raised, as of 2026-09-10)
A pure-JSON plugin publishes fine with "mainClass": null — the platform loads it via a built-in generic entry point, no jar required. Only set mainClass if your plugin ships its own Java extensions.
plugin.json is missing id/version
Fill them in. id is lowercase kebab-case; version is semver.
could not reach <url> / ERR_INVALID_URL
Wrong env base URL (erp env list), the server is down, or — on Git Bash — MSYS_NO_PATHCONV=1 needed.
Stage VALIDATING
Refusing to overwrite immutable artifact at "system/plugins/<id>/<version>/..."
You already stored a .spk under this <version>, the new bytes differ, and that version genuinely shipped (it reached pf4jState: STARTED on some tenant). A shipped version is write-once — bump to the next version.
As of the 2026-09-10 publish-hardening pass this is no longer raised for a version whose prior install failed or never started (a role clash caught late, or a metadata-only plugin that isn't PF4J-loaded): re-publishing the same version now replaces the stored .spk and retries. So the "edit a page, re-publish the same version" loop works for JSON-only plugins without a bump. You still bump for every change to a version that's live and STARTED.
one or more page definitions failed block-property validation
A page sets a property that the block type doesn't have, or uses a block type that doesn't exist. Run erp plugin validate spk-assembly locally — it reports which page and which property. Ground the block first with erp blocks list --type <name>.
schema validation failure on an artifact
The named file doesn't match its schema. Run erp schema validate <file> --schema <name> and node developer-docs/examples/test-examples.mjs.
Stage LOADING
There is an already loaded plugin ... with the same id (...)
A previous version of this plugin id is still loaded in PF4J. The platform cannot hold two versions of one id at once.
Normal upgrade / in-place re-publish: handled — the publish path now stops + unloads the currently-loaded same-id plugin (with a GC hint) before loading the new bundle.
erp plugin publish/spark publishalso auto-retry once with a force-unload if they still see this error.Wedged load (
state: uninstalledinerp plugin listbutpf4jState: STARTED): a crash or a transientFailed to obtain JDBC Connectionduring uninstall aborted the unload. Recover without a backend restart:basherp plugin force-unload <plugin-id> erp plugin publish <plugin>-<next>.spk --tenant 2force-unloadbest-effort stops, unloads and GC-hints the stuck classloader, then reportspf4jState(NOT_LOADEDon success). It's idempotent and does not need an active install row.erp plugin publish --forceruns it for you.
plugin manager rejected the package (missing/invalid plugin.json or mainClass?)
The lib/*.jar doesn't contain the class named by mainClass, or isn't a valid plugin jar. Recompile the stub; confirm jar -tf spk-assembly/lib/*.jar lists your .class file at the right package path.
After code load (rolled back)
A role named "X" already exists
Another installed plugin owns a role with that display name. Rename yours.
Role "X" (code "Y") already exists owned by "Z"
Another plugin owns that role code. Change your code. (This survives a plugin's own uninstall — a role created by example-plugin blocks office-equipment from creating one with the same code.)
seed-data failure — entity not found
A metadata/seed-data/*.json targets an entity your plugin doesn't ship. Ship the metadata/entities/*.json (or, for a shared config entity, ship the additive version of it).
Verify a successful publish
bash
erp plugin listYour plugin: "state": "installed", "pf4jState": "STARTED", "migrationStatus": "INSTALLED". Then hit a page or entity endpoint to confirm the new content is live.