Skip to content

Publish and upgrade a plugin

What you're doing

Getting the .spk onto your ERP and, later, shipping a new version.

The complete sequence

bash
# 1. build the .spk (a zip of spk-assembly/)
erp plugin build spk-assembly -o office-equipment-1.0.0.spk

# 2. publish it to the current env, for a tenant
erp plugin publish office-equipment-1.0.0.spk --tenant 2

Output on success:

packaged 33 files -> office-equipment-1.0.0.spk (231986 bytes)
sha256: cef54cdf...
erp plugin publish: office-equipment-1.0.0.spk -> https://erp.example.com (env "dev", tenant 2)
POST .../api/v1/authoring/plugins/upload ...
installed:
{"pluginId":"office-equipment","version":"1.0.0","state":"installed","pf4jState":"STARTED", ...}

"state":"installed" and "pf4jState":"STARTED" mean it's live. The server recomputes the checksum itself — it never trusts the client digest.

What happens on install

  1. checksum verified server-side;
  2. the manifest is read (id and version are required; mainClass is optional — a pure-JSON plugin leaves it null);
  3. PF4J loads the plugin — lib/*.jar if present, otherwise a built-in generic no-op entry point for a pure-JSON plugin;
  4. entities are created / altered (idempotent-additive);
  5. providers, data views, data services, pages, menus, rules, workflows, i18n and roles are installed;
  6. seed data is upserted;
  7. AFTER_CREATE register rules fire → scheduled jobs register.

If any step fails the whole install rolls back, including the PF4J load.

Version discipline — the rules that will bite you

1. Bump the version before every publish

plugin.json version must increase. A re-publish of a version that's already installed is a silent no-op — your fix does not ship. Bump it even if you're certain the last publish already contained the change.

2. A version is immutable once stored

Once the server has stored office-equipment/1.0.0/office-equipment-1.0.0.spk, re-uploading different bytes under 1.0.0 is rejected:

Refusing to overwrite immutable artifact at "system/plugins/.../1.0.0/..." with different content

A version that genuinely shipped (reached pf4jState: STARTED on some tenant) stays immutable forever — re-publishing different bytes under it is rejected, go to the next version.

But a failed or partial install is not a real release. As of the SDK publish-hardening pass, if a 1.0.0 publish never reached STARTED (it failed later in the pipeline, or it's a metadata-only plugin that isn't PF4J-loaded), re-publishing 1.0.0 replaces the stored .spk and retries — no version bump needed just to fix a broken publish. erp plugin publish of an already-installed id automatically takes the upgrade path, so this "edit a page, re-publish the same version" loop works directly for JSON-only plugins.

3. The platform cannot load two versions of the same plugin id at once

There is an already loaded plugin ... with the same id (office-equipment) ...
Simultaneous loading of plugins with the same PluginId is not currently supported.

The publish/upgrade path now reliably stops and unloads the currently-loaded same-id plugin before loading the new bundle (stop → unload → GC hint → load → start), so a normal in-place re-publish never hits this. If an earlier crash or a transient DB error during uninstall left the old code wedgedstate: uninstalled in the install row but pf4jState: STARTED in the process — recover without a backend restart:

bash
erp plugin force-unload office-equipment      # clears the stuck PF4J load
erp plugin publish office-equipment-1.0.1.spk --tenant 2

erp plugin publish --force does the force-unload automatically before retrying, and spark publish / erp plugin publish also auto-retry once with a force-unload if they see the "already loaded" error.

These behaviours were all hit while building the tutorial module. Treat version numbers as cheap and monotonic; use force-unload rather than a restart when a load gets stuck.

Roles and names must be unique across installed plugins

A plugin.json role whose name or code collides with a role from another installed plugin fails the install:

A role named "Equipment Manager" already exists
Role "..." (code "...") already exists owned by "..."

Prefix your role names and codes with something plugin-specific.

Upgrading

bash
# edit files, bump plugin.json version to 1.0.1, rebuild the lib jar if Java changed
erp plugin build spk-assembly -o office-equipment-1.0.1.spk
erp plugin publish office-equipment-1.0.1.spk --tenant 2

Entities gain new columns; pages, rules, seed data are re-installed (upserted). Nothing is dropped — a removed field's column stays, a removed seed row stays.

How to verify it worked

bash
erp plugin list

Find your plugin: "version": "1.0.1", "state": "installed", "pf4jState": "STARTED". Then hit a page or an entity endpoint to confirm the new content is live.

Common mistakes

SymptomCauseFix
"fix didn't ship"didn't bump version on a shipped versionbump; re-publish. (A failed same-version publish now retries in place.)
Refusing to overwrite immutable artifactretrying a version that already reached STARTED on some tenantgo to the next version
already loaded plugin ... same ida load wedged after a crash / DB bliperp plugin force-unload <id> then re-publish (or publish --force)
role name/code clashanother plugin owns that roleprefix yours