Appearance
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 Java1. Create the plugin
Open Plugin Studio. On the empty screen choose Guided setup (or File, New plugin). The wizard has six steps:
- Name - type "Equipment Loans"; the id
equipment-loansfills in by itself (lowercase letters, digits and dashes; it cannot change later). - Type - full application, UI plugin, backend plugin, extension or integration. This decides which steps follow.
- Data - Add data, call it
loan, add a fielditem. Data names aresnake_case. - Roles - Add suggested roles creates an admin and a viewer role holding the create, read, update and delete permissions for your data.
- Screens - a list screen and a menu entry for your first data.
- 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:
| Add | You fill in | What appears |
|---|---|---|
| Form | title and the fields (label, kind) | a form the designer opens next |
| Mobile navigation | title, layout, links | a bottom-tab or drawer definition |
| Report / Print template | title, the data, the columns | bands with a header and one row per record |
| Business rule | when it runs, Only if conditions, Then reject or set a field | a rule file (use Edit as JSON for lookups and expressions) |
| Translations | key 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.jsonchange; 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)
- Studio, Plugin Studio, Guided setup. Name
Equipment Loans, keep Full application, add dataloanwith a fielditem, add suggested roles, keep the screen, Create plugin. - Build, Add Java backend (starter)... then Build, Compile Java - the Output panel ends with
BUILD SUCCESS. - Build, Run Java tests -
Tests run: 1, Failures: 0. - Deploy, Publish... - every step turns green and the dialog says
Published version 1.0.0. - Header shows Installed. Open the launcher: the plugin's menu entry is there.
- 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 see | Why | Fix |
|---|---|---|
| 403 on a builder call | your account lacks the Studio Staff role | assign it (tenant administrators get it at registration) |
| Studio sends you back to the login page | your account lacks the Studio Staff role | assign it (tenant administrators get it at registration) |
| "Role name is required" on install | an older plugin made before the builder wrote role names | open 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 running | wait a moment and try again |
| Compile fails with "package ... does not exist" | the plugin uses a library that is not in the worker image | add 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 off | tick Raise the version automatically or raise it in Edit, Plugin properties... |
See also
- Create a plugin from scratch (the command-line route)
- Validate and test
- Publish and upgrade
- Build a code plugin
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-managerThe 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:
| Change | What it does |
|---|---|
| Change a setting | Sets one value inside a block, for example properties.text.value to Team roster |
| Remove | Takes a block out |
| Replace | Swaps a block for another |
| Add before / Add after | Adds a block next to one |
| Add into a list | Adds 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).
