Skip to content

Create, build, test and publish a plugin in Plugin Studio ​

What you're doing ​

Taking a plugin from an idea to something installed for your tenant, using only Plugin Studio (no terminal). You create it with a wizard, add screens, data, rules and Java or Node code, check it, save a way back, and publish it. Every step below also has a command-line equivalent in the other guides, so you can mix the two.

Before you start ​

  • You can sign in to Studio (the Admin tile on the launcher). Studio opens for accounts with the Studio Staff role; the administrator of every tenant gets it when the tenant is registered.
  • The builder (create, validate, build, compile, test, publish, write source code) is part of Studio itself: the Studio Staff role is all you need. It needs no permission from any application such as HCM.

The whole journey ​

Create  ->  Add things  ->  Check  ->  Snapshot  ->  Compile / test  ->  Build  ->  Install  ->  Roll back if needed
wizard      Explorer +      Validate   File menu     Build menu         Publish   Publish      Deploy menu
            Add Java

1. Create the plugin ​

Open Plugin Studio. On the empty screen choose Guided setup (or File, New plugin). The wizard has six steps:

  1. Name - type "Equipment Loans"; the id equipment-loans fills in by itself (lowercase letters, digits and dashes; it cannot change later).
  2. Type - full application, UI plugin, backend plugin, extension or integration. This decides which steps follow.
  3. Data - Add data, call it loan, add a field item. Data names are snake_case.
  4. Roles - Add suggested roles creates an admin and a viewer role holding the create, read, update and delete permissions for your data.
  5. Screens - a list screen and a menu entry for your first data.
  6. Review - lists every step it will run. Create plugin runs them in order and shows each one succeed or the one that stopped.

Prefer to describe it in words? On step 1 type a sentence in Describe your plugin and choose Draft with AI. The wizard closes and the Studio assistant opens with your request in its input. Nothing is created until you send it.

2. Add what the plugin needs ​

The left Explorer groups the plugin's parts: UI, Data, Backend, Workflow, Security, Integration, Localization, Reports, Tests. Every group has a + that opens a plain form:

AddYou fill inWhat appears
Formtitle and the fields (label, kind)a form the designer opens next
Mobile navigationtitle, layout, linksa bottom-tab or drawer definition
Report / Print templatetitle, the data, the columnsbands with a header and one row per record
Business rulewhen it runs, Only if conditions, Then reject or set a fielda rule file (use Edit as JSON for lookups and expressions)
Translationskey and text rows (or Paste JSON)an English translation file

Open any item to edit it in the same designers the rest of Studio uses. The Designers menu opens every other designer (pages, entities, workflows, access policies, agents, test cases, data import).

3. Add backend code (optional) ​

Build, Add Java backend (starter)... writes a working starter into the plugin: a pom.xml, a plugin class (com.erp.plugins.<id>.<Name>Plugin) and a unit test, and sets mainClass in plugin.json. Edit the Java files under Advanced, Java source in the Explorer. For Node code, put a package.json with test and build scripts in the plugin folder (import a plugin that has one, see step 8).

4. Check it ​

Validate (header button or Build menu). The header shows Valid, N warnings or N errors. Click the chip to open the Problems list; each problem opens the screen it is about.

5. Save a way back ​

File, Save snapshot... stores the plugin's whole source under a name. File, Restore snapshot... brings one back (it first snapshots the current state, so a restore can itself be undone). Publish takes one for you.

6. Compile and test the code ​

From the Build menu, each on its own:

  • Compile Java - mvn compile
  • Run Java tests - mvn test; the output shows how many tests ran and how many failed
  • Package Java (jar into the plugin) - builds the jar and puts it in the plugin's lib/ folder, replacing the older jar of the same plugin
  • Node: install / run tests / build - npm ci, npm test, npm run build

Their output appears in the Output panel. A failing step tells you which lines went wrong.

7. Publish ​

Deploy, Publish... runs everything in order and stops at the first failure:

Check the plugin for problems
Save a snapshot to go back to
Compile the Java code          (only if the plugin has Java)
Run the Java tests             (only if the plugin has Java)
Package the Java code (jar)    (only if the plugin has Java)
Install, test and build Node   (only if the plugin has Node code)
Make sure the version is new   (raises the patch version if it is already installed)
Build the package
Fetch the built package
Install it for this tenant     (a fresh install, or an upgrade)

If a step fails, the dialog names it, shows the error lines, and offers Restore snapshot.

8. Move a plugin in or out ​

  • File, Export plugin as .zip downloads the whole plugin (screens, roles, Java, Node, everything except build output and snapshots).
  • File, Import plugin (.zip / .spk)... uploads one. It refuses an id that already exists.
  • File, Save as... and Clone copy the plugin under a new id. Only the id and name in plugin.json change; names inside the plugin (provider names, form ids, routes) keep the old id.
  • Edit, Plugin properties... changes the name, version and description.

9. Roll back ​

  • Before the plugin is installed: File, Restore snapshot....
  • After it is installed: Deploy, Roll back installed version puts the previously installed version back (one version). The platform refuses if there is none.

How builds run (why they are separate from the ERP backend) ​

Your build never runs inside the ERP backend. Each build is sent to a small dispatcher, which starts a fresh container just for that build, runs the steps (validate, compile, test, jar, npm, spk), collects the result and the files, and deletes the container. Nothing stays running between builds except the tiny dispatcher.

  • The container has no network unless a job needs npm, has CPU, memory and process limits, drops all privileges, and is killed if it runs longer than 15 minutes.
  • Java builds work offline against the libraries provided in the build image. A plugin that needs another library has to have it added to that image.
  • Plugin code runs in that throwaway container (tests and npm scripts execute what the plugin author wrote), which is why it is isolated.

Try it end to end (a runnable example) ​

  1. Studio, Plugin Studio, Guided setup. Name Equipment Loans, keep Full application, add data loan with a field item, add suggested roles, keep the screen, Create plugin.
  2. Build, Add Java backend (starter)... then Build, Compile Java - the Output panel ends with BUILD SUCCESS.
  3. Build, Run Java tests - Tests run: 1, Failures: 0.
  4. Deploy, Publish... - every step turns green and the dialog says Published version 1.0.0.
  5. Header shows Installed. Open the launcher: the plugin's menu entry is there.
  6. Deploy, Roll back installed version if you want to go back (it needs a previous version, so publish once more first).

What can go wrong ​

You seeWhyFix
403 on a builder callyour account lacks the Studio Staff roleassign it (tenant administrators get it at registration)
Studio sends you back to the login pageyour account lacks the Studio Staff roleassign it (tenant administrators get it at registration)
"Role name is required" on installan older plugin made before the builder wrote role namesopen the plugin's roles and give each role a name, or recreate the role
"the build service refused the job (503)"two builds are already runningwait a moment and try again
Compile fails with "package ... does not exist"the plugin uses a library that is not in the worker imageadd the dependency to the worker image (deploy/builder/seed/pom.xml) or use only the platform API
Publish stops at "Make sure the version is new"that version is already installed and automatic raising is switched offtick Raise the version automatically or raise it in Edit, Plugin properties...

See also ​

Run a plugin's backend service, and control it ​

Deploying a plugin's Java backend starts it as its own container, separate from the ERP backend. Built jars stay on the build host and are never sent as JSON or held in memory. Open Deploy, Manage backend service… in Studio to:

  • see whether it is running, its health, version and live CPU/memory;
  • Start, Stop, Restart it (Stop keeps the container and its settings);
  • change CPUs (0.25 to 4) and memory (128 MB to 4 GB), which apply immediately, and add environment variables, which restart it;
  • read the logs and run an earlier version.

Everything Studio does is also available from the command line and to an AI agent, using the same calls:

bash
erp plugin op                                   # list every operation
erp plugin op java-compile --plugin-id leave-manager
erp plugin op service-deploy --plugin-id leave-manager
erp plugin op service-set-config --plugin-id leave-manager --cpus 2 --memory 1g
erp plugin op service-logs --plugin-id leave-manager --tail 100
erp plugin op service-restart --plugin-id leave-manager

The MCP server exposes the same operations as one tool, erp_plugin_op ({"op":"service-restart","pluginId":"leave-manager"}). Both use your erp login session, or ERP_TOKEN.

How builds and the jar move (and why it stays fast) ​

Nothing large is ever put in JSON or held in memory. Studio's backend zips your plugin to a temporary file and streams it to the build host. The build host runs the steps in a disposable container and streams back a small zip with the results. A built backend jar (often 20 MB or more) never travels: it stays on the build host, and Deploy backend service runs that jar there. If the host has no built jar, Deploy tells you to run Build, Package Java first.

Routing to your service ​

When a backend service starts, it registers itself with the platform (a shared secret proves it is genuine) and keeps sending a heartbeat. Registration adds a route for /api/v1/plugins/<your-plugin>/** to the gateway's route table, so the gateway sends those requests straight to your service within a few seconds, with the signed-in tenant and user already attached. Nothing to configure. If the service stops cleanly the route is removed, and calls go back through the platform's own proxy, which answers "temporarily unavailable" until it is back.

Running in more than one region ​

Run one stack per region (gateway, platform, build host) and give each the same PLUGIN_REGION=<region code> in its .env. The region must exist in the platform's regions with an active deployment target. Services started in a region register for it, and that region's gateway routes to its own copy. A region with no copy of a service falls back to the platform's proxy.

Enhance an installed plugin for your business (HCM, CRM, any plugin) ​

You never change an installed plugin. That is how upgrades stay safe, and it is how SAP, Odoo, Dynamics 365, Salesforce and ERPNext all work. In Installed, click Customize… on the plugin. Two routes:

  • Extend it (recommended). Studio starts a plugin of your own that depends on the installed one. You add your own data, screens, rules and workflows. The original keeps receiving its updates. Your plugin says which versions of the original it is built for (for example >=2.0.0 <3.0.0), and the platform refuses to install it against a version outside that range.
  • Copy it. Studio makes an editable copy under a new id and version 1.0.0, with the original's screens, data definitions and workflows (and its Java source, when the package shipped it). Publish it as your own plugin. A copy is a fork: you take over merging any later change to the original, and it keeps the original's artifact names, so install it in place of the original for your tenant, not next to it. Compiled libraries and database migrations are never copied.

Only a plugin that allows extension ("extendable": true) can be extended. From the command line: erp plugin op extend-installed --plugin-id hcm-core --newPluginId acme-hcm --newName "Acme HCM" (or copy-installed).

Source in the package ​

The packaging tool puts the plugin's src/ and pom.xml in the package as source/, and the platform stores the package. That is what lets a tenant copy a Java plugin later. A publisher who protects its source (marketplace plugins should) sets "shipSource": false in plugin.json, or packages with --no-source.

Manifest fields ​

plugin.json accepts extendsPlugin (the installed plugin an extension builds on, also listed in dependencies), extendsVersionRange, clonedFrom ("id@version", informational), description and shipSource.

Change a shipped page for your organization (layered overrides) ​

You can change how a plugin's page, form or dashboard looks for your organization without copying it and without losing the plugin's updates. The plugin's version stays exactly as shipped. Your changes are kept on their own and applied on top, so when the plugin is upgraded your changes ride on the new version.

Open the plugin's page in its designer and click Customize… (it appears on plugin-shipped, read-only artifacts). Add changes:

ChangeWhat it does
Change a settingSets one value inside a block, for example properties.text.value to Team roster
RemoveTakes a block out
ReplaceSwaps a block for another
Add before / Add afterAdds a block next to one
Add into a listAdds an item to a list inside a block, for example a grid column

Each change points at a block by its instance id (the editor suggests them). Check shows how many changes apply to the current version, and Save changes stores them. Use the shipped version removes all your changes.

If a plugin upgrade removes or renames a block a change points at, that change is skipped (the page still works) and the editor lists it so you can fix it. The upgrade's audit-log entry also says "N of M overrides no longer fully apply". From the command line: erp plugin op overrides, override-preview, override-set, override-clear and override-report.

bash
erp plugin op override-set --type page --name active-employees \
  --json '{"operations":[{"op":"set","target":"header-title","path":"properties.text.value","value":"Team roster"}]}'

Extension points: run your own service at a named moment ​

An extension point is a named moment in a plugin's logic where your own extension service can step in, without changing the plugin. Bind your service to a point and the platform calls it (with a 2-second timeout and a circuit breaker; if your service fails, the platform carries on as if it were not there).

Built in for every Entity Engine record of every app: EntityRecordBeforeCreate, EntityRecordBeforeUpdate (both let you return field values to merge in), EntityRecordBeforeDelete (answer {"valid": false, "message": "..."} to stop a delete), and EntityRecordAfterCreate, EntityRecordAfterUpdate, EntityRecordAfterDelete (informational). List every point, built in and plugin-declared, with erp plugin op extension-points.

A plugin adds its own points in plugin.json, for example "extensionPoints": [{ "code": "PayrollBeforeCalculation", "semantics": "BEFORE", "description": "Runs before pay is calculated" }], and calls the extension router at that spot in its code. A point that fully replaces the plugin's own logic (REPLACE) needs an elevated permission to bind.

Upgrading a plugin that has extensions ​

Before a plugin is upgraded for a tenant, the platform checks every extension installed for that tenant against the new version. If an extension is not built for it (outside its declared version range), the upgrade is stopped and lists the extensions. Upgrade those first, or upgrade anyway with force=true (--force-upgrade in the CLI), which is recorded in the audit log (the upgrade's entry says what it went past).