Modules (NXM)
Develop, debug and test
How you build an NXM module with the nexxus extension for VS Code or the command line nxm. Problems show up on save, every change runs in a scratch tenant right after, services run under the debugger, and a log per installation shows what happened.
Building a module should feel like building a web app: you save a file and see the change a moment later, in a real nexxus with your sample data. When something goes wrong, you read the reason in a log instead of guessing. And before you hand a package on, you already know whether nexxus will take it.
That is what the extension nexxus Module for Visual Studio Code is for. It checks your package on save, installs it in a scratch tenant and keeps it current there with every save. It does not know the rules itself: every package goes to the instance, which runs the same checks as an upload. Without VS Code, and for coding agents, the command line nxm does the same.
The extension speaks German for now. This page quotes its commands as it shows them; the labels of the app are quoted in English.
Create a package
A package that installs as it is: manifest, view, sample data.
Connect
To an instance, with your personal API token.
Dev-Modus
Every save is installed in the scratch tenant, the open view reloads.
Debug
Breakpoints in your services and views, as in any web project.
Log
Service calls, migrations and view errors, live and with replay.
What you need
Section titled “What you need”- Visual Studio Code 1.90 or later.
- The extension as the file
nexxus-modules.vsix. It is not on the Marketplace. You get it from your contact at Systaro; with access to the nexxus GitLab, download the latest here. Install it in VS Code from the Extensions view, menu..., Install from VSIX…, or on the command line withcode --install-extension nexxus-modules.vsix. A newer file installs the same way over the old one. - An instance with scratch tenants. A nexxus you run from the sources with the Spring profile
developmentorlocalhas them out of the box. On any other instance its operator switches them on, withnexxus.modules.scratch.enabledandnexxus.modules.scratch.domain-template, the host per scratch tenant (for example{slug}.module.example.com, with DNS and a certificate for those hosts). - An administrator account on that instance, and a personal API token for it.
- Deno 2, only when your package has services of its own (installation).
Create a package
Section titled “Create a package”Open an empty folder and click the cube nexxus Module in the activity bar. The sidebar offers Neues Paket anlegen (create a new package). The command asks for the key (acme.tasks: publisher and name, in lower case) and the name in the app, and writes:
Directoryacme.tasks/
Directory1.0.0/
- module.json one business object with list and record view, a page of its own, an action
- CHANGELOG.md
Directoryui/
- offen.html
Directorydemo/ sample records
- …
The package installs as it is. The extension then offers to start the Dev-Modus on it. The files are ordinary files: you edit them in the explorer, and the extension checks and installs them on save. How the parts fit together is in Your first module.
Connect to an instance
Section titled “Connect to an instance”-
Create an API token in the app, as described in Personal API tokens. The account has to be an administrator of the tenant: checking and uploading is the same right as uploading in the app.
-
Run the command nexxus: Mit Instanz verbinden (connect to instance) in VS Code. Give the address the API answers on, for example
https://nexxus.example.com, and paste the token (cx_...).
The address must start with https. Only localhost, 127.0.0.1 and [::1] also work with http, because every call carries an administrator’s token. The extension checks the token before it keeps it, and stores it only in your operating system’s keychain, never in settings or files. The status bar shows the instance; clicking it reconnects or disconnects.
Problems on save
Section titled “Problems on save”The extension checks every module.json against the nexxus JSON schema as you type, even without a connection: completion of the fields, their types, and a mistyped entitys or a version 1.0 are underlined.
With a connection, every save sends the whole package to the instance (POST /api/module-packages/validate). The instance checks it as it checks an upload and, when the tenant has the package installed, also by the rules of an upgrade against the installed version. It stores nothing. Each problem shows in the Problems panel at its place:
- in
module.jsonand in the files underdemo/at the JSON path the check names, or at the nearest existing part above it when a property is missing, - on the first line of its file when it has no path (
CHANGELOG.md, templates), - on the first line of
module.jsonwhen it has no file.
A problem you fixed disappears on the next save.
The Dev-Modus: changes visible on save
Section titled “The Dev-Modus: changes visible on save”Scratch tenants
Section titled “Scratch tenants”The Dev-Modus works in a scratch tenant: a tenant of its own on the instance, empty but for an administrator who stands for you. Nothing you do there touches another tenant. A scratch tenant expires after 7 days, you can extend it up to 30 days after it was created, and each person has at most 5. Once it has expired, the instance deletes it with its packages and data within an hour. In the app a slim bar at the top reads Scratch tenant “Tasks” · expires 02.11.2026.
The sidebar lists your scratch tenants under Testmandanten, with their expiry dates. Its buttons and context menu create one (Testmandant anlegen), open it in the browser already signed in (Testmandant im Browser öffnen), extend or delete it, and Für den Dev-Modus verwenden makes the Dev-Modus take it without asking.
Start the Dev-Modus
Section titled “Start the Dev-Modus”Start it on the version folder in the sidebar (lightning icon), from the context menu of a file in that folder, from the Dev-Modus entry in the status bar, or with nexxus: Dev-Modus starten. The first time, the extension asks which scratch tenant to use, or creates one. Then:
- It packs the folder in memory as a dev version, for example
1.2.0-dev. Yourmodule.jsonon disk stays as it is and still says1.2.0. - The first time, the instance installs the package with its sample data and gives you every role of the package, without a dialog.
- After that, the extension installs again on every save of a file in the folder, shortly after the last save. The instance applies the version like an upgrade: your records stay, and migration steps that have not run yet run. So a broken migration shows on the save that broke it.
- Open views of the module reload in place, on the same page.
While an install runs, the status bar shows a spinner, and its duration afterwards. Problems of the package go to the Problems panel as above. Clicking the entry opens its menu: the scratch tenant in the browser, the log, tracing an action, debugging a service or the views, resetting the data or ending the Dev-Modus.
A dev version needs no heading of its own in CHANGELOG.md: ## [1.2.0] also covers 1.2.0-dev. Migration steps go under the release version as always ("1.2.0"). A released version stays fixed as before: nexxus refuses the same version with other content. Only a dev version takes new content, and an installed one only through the Dev-Modus.
When the instance refuses a step
Section titled “When the instance refuses a step”Some things nexxus does not allow in an upgrade, such as a change that would lose data the scratch tenant already has. The extension then shows the reason. Where it helps, it offers Daten zurücksetzen (reset data): the package is removed with its data and installed again with the sample data; the rest of the scratch tenant’s data stays. The package page in the app has the same button, Reset data, in scratch tenants only.
Services on your machine
Section titled “Services on your machine”If your package brings its own services (services in module.json), their code lives in the version folder under services/<service>/main.ts. That folder does not go into the package. nexxus: Dienst anlegen (create service) writes the start of a service the manifest names: main.ts with an event handler and an action, and a deno.json that points @nexxus/module-kit at the service kit. The extension puts the kit under .nexxus/kit/ in your workspace folder, with a .gitignore of its own.
In the Dev-Modus every service runs on your machine, with deno serve --watch --inspect and PLATFORM_URL set to the instance. Deno restarts it on every change of its files. The instance reaches it through a tunnel the extension keeps open: the scratch tenant’s calls to the service’s address go to your machine, without you exposing an address or consenting to a new one. Each service writes to an output channel of its own, nexxus Dienst: acme.tasks/deadlines, one line per call with status, duration and call ID.
The kit answers /<name>/events and /<name>/actions/<operation>, with the name main.ts gives the kit. The service’s address in module.json therefore ends in that name, for example http://localhost:8390/fristendienst.
When the Dev-Modus is not running, a call fails at once, and the log says the local service is not connected and asks you to start the Dev-Modus in VS Code. Events wait meanwhile; once the tunnel is open, the instance delivers them within a few seconds.
Debugging
Section titled “Debugging”The extension brings the debug type nexxus. It starts the Dev-Modus if it is not running yet and hands over to VS Code’s JavaScript debugger:
- A service: press F5 in a file under
services/<service>/. The debugger attaches to the service, and again after every restart by--watch. Breakpoints inmain.tshold. - The views: F5 in a file under
ui/opens Chrome in the scratch tenant, already signed in. Breakpoints inui/*.htmlhold.
A .vscode/launch.json sets both up:
{ "version": "0.2.0", "configurations": [ { "type": "nexxus", "request": "attach", "name": "Debug deadlines", "folder": "${workspaceFolder}/acme.tasks/1.2.0", "service": "deadlines" }, { "type": "nexxus", "request": "launch", "name": "Debug views", "folder": "${workspaceFolder}/acme.tasks/1.2.0" } ]}In the browser’s developer tools
Section titled “In the browser’s developer tools”A view runs in a sandboxed frame inside the page, so the browser’s developer tools work as usual. Its scripts show there under the view’s file name, such as ui/offen.html, with the same line numbers as in the editor. What the view writes with console.log or console.error, and errors it does not catch, appear in the console of the page. The frame’s address is about:srcdoc and its origin is null: to run commands in the view’s document, choose that frame as the context of the console. A fetch to your own server fails on purpose, because the sandbox allows no network; use nx.records and the other SDK calls instead. How the view and nexxus talk is in The protocol, for debugging.
See what happened: the log
Section titled “See what happened: the log”Every installation has a log. You find it on its package page under AdministrationOperationsModules, tab Packages, in the tab Log, and in VS Code with nexxus: Protokoll in a panel beside the editor. Both show new entries as they happen and filter by kind and level. The log holds, with time and outcome:
- install, upgrade, reset, activate, deactivate and uninstall, and every migration step that ran,
- uploads and Dev-Modus installs that nexxus refused, with every problem found,
- every call of a service: action, event delivery, customer portal endpoint, with status, duration, the error and a call ID the service gets too (
X-Nexxus-Correlation-Id), so you find the same call in your service’s output, - for dev versions only: the request sent and the response of every service call, every declarative action that ran with its evaluated values, and what your views write with
console.erroror do not catch.
For entries that recorded a service call of a dev version, Send again sends it to the service once more, in the Dev-Modus to the one on your machine: set a breakpoint, send again, step through. The new call shows as an entry of its own.
The log goes back 30 days. The AI assistant can read it for administrators too, for example when you ask it why a delivery fails.
Trace an action
Section titled “Trace an action”nexxus: Aktion durchspielen (trace action) asks for an action of the package, a record and the inputs the action reads. The extension then asks the instance what the action would do. Nothing runs, nothing is written. The panel shows whether it would run and what it would do, whether you hold the right for it, every condition from when with the value in the record, and every template such as {{record.title}} with the value it renders to. Erneut durchspielen asks again with the same inputs, for example after a save. If the instance refused the last save, it traces the last installed state, and the panel says so.
Testing
Section titled “Testing”Services Available. The service kit comes with a fake platform for tests: FakePlatform.start() serves the keys, signs invocation tokens like nexxus, runs the token exchange and keeps records in memory behind the record API. seed(module, entity, data) puts records in, list(module, entity) reads them back, stop() ends it. It lives in the nexxus sources under module-services/src/kit/testing/; the tests of the Fristendienst use it, in module-services/src/fristendienst/service.test.ts. Run them with bun test. The copy of the kit the extension writes does not include it.
Views Planned. A fake host stands in for nexxus, so a view can be tested with Vitest: you give it records and context, the view runs, and you check what it called and showed. Until then, test views in the Dev-Modus.
Upload
Section titled “Upload”nexxus: Hochladen (upload) builds the package, uploads it to the connected instance and opens its package page. The command never installs: an administrator does that on that page, choosing roles and consenting. It uploads the version module.json names, never the Dev-Modus’s dev version. nexxus: Paket bauen (build package) only writes the file, as dist/<key>-<version>.nxm in the workspace folder.
The extension builds unsigned packages. Outside the profiles development, local and demo, an instance only takes them when an administrator of the tenant has switched on Allow unsigned packages on the tab Publishers. You build a signed package on the command line, see Trust the publisher.
On the command line and in CI
Section titled “On the command line and in CI”Without the nexxus sources, use nxm validate and nxm build (above) with NXM_URL and NXM_TOKEN set. With the sources, the Gradle task validatePackage checks a package folder the way nexxus does: schema, every rule, the templates and the data files. Run it in backend/:
./gradlew validatePackage -Pdir=/path/to/acme.besucher/1.1.0 # one version folder./gradlew validatePackage -Pdir=/path/to/acme.besucher # every version of a package./gradlew validatePackage -Pdir=/path/to/acme.besucher/package -Pprevious=/path/to/last-releaseA folder named like a version must sit in <key>/<version>; a folder with any other name holds the current version of a package, for example in a repository of its own that publishes versions as Git tags. -Pprevious compares with the last release and checks the rules of an upgrade: a higher version number, nothing removed or redefined without a new major version. The task names every folder it checked, lists every problem when there is one, and ends with an error, so a CI job fails. node backend/scripts/module-package.mjs verify <file>.nxm checks a built package: hashes, file list and the heading in the changelog.
Without VS Code: nxm
Section titled “Without VS Code: nxm”nxm is the same loop as a command line, for any editor, for scripts and for coding agents such as Claude Code, Codex or Cursor. Like the extension it talks to an instance and runs nothing of nexxus on your machine; only your own services run locally, under Deno.
npm install -g @nexxus-platform/nxm # Node.js 20.12 or laternxm init acme.tasks # a package that installs as it is, plus agent skillsnxm login https://nexxus.example.com # once; asks for your personal API tokennxm dev # installs into the scratch tenant on every saveThe token goes into your operating system’s keychain, as with the extension. Both use the same scratch tenants of an instance.
| Command | Does |
|---|---|
nxm validate | Checks locally, then with the rules of the instance; problems print as file:line:col: message |
nxm dev | Installs into the scratch tenant on every save and runs the services under Deno with an inspector on port 9229 |
nxm apply | Installs once and ends; --reset --yes starts over with the sample data |
nxm logs, nxm logs -f | The installation’s log, as in the panel |
nxm replay <id> | Sends a recorded service call again |
nxm trace <action> --record <id> | What an action would check and do for a record, without doing it |
nxm open | Opens the scratch tenant, signed in as its administrator |
nxm build, nxm verify | Builds the .nxm (signed with --key) and checks one |
nxm upload | Uploads the version to the connected tenant; an administrator installs it there |
nxm help <command> lists the options. With --json every command prints one JSON line (nxm logs one per entry, nxm dev one per event), and the exit code is 0 when done, 1 when it failed, 2 when called wrongly. In CI, set NXM_URL and NXM_TOKEN instead of logging in.
Coding agents. nxm init and nxm skills write skills to .claude/skills/ and an AGENTS.md that points to them. They teach an agent the package format, the rules nexxus enforces and the loop with nxm, and they carry this documentation and the manifest’s JSON schema as references, in the version that matches the installed nxm. An agent then checks and installs its own changes with nxm validate and nxm apply and reads the result in nxm logs. It never needs the token: the person signs in once with nxm login.