Module (NXM)
Aufbau eines Pakets
Die Referenz für NXM-Pakete. Welche Dateien ein .nxm-Archiv enthält, jedes Feld des Manifests module.json mit Typ und Regeln, die Schlüssel, die nexxus daraus ableitet, und wie nexxus ein Paket prüft, bevor es angenommen wird.
Ein NXM-Paket ist ein Ordner mit einem Manifest, einem Changelog und ein paar optionalen Dateien, gepackt als ZIP-Archiv mit der Endung .nxm. Fast alles, was ein Modul ausmacht, steht im Manifest module.json: seine Fachobjekte, Oberflächen, Menüeinträge, Aktionen, Rollen und das, worauf es zugreifen darf. nexxus liest diese Datei und baut das Modul daraus. Diese Seite beschreibt jede Datei und jedes Feld. Ein vollständiges Beispiel Schritt für Schritt finden Sie unter Ihr erstes Modul.
Die Dateien eines Pakets
Abschnitt betitelt „Die Dateien eines Pakets“Ordneracme.besucher/1.0.0/
- module.json das Manifest (Pflicht)
- CHANGELOG.md ein Abschnitt je Version (Pflicht)
Ordnerui/
- empfang.html eine Datei je eigener Ansicht
Ordnertemplates/
- brief.html ein Brief, den eine Mail-Aktion füllt und als PDF versendet
Ordnerdata/
- besuch.json Datensätze, die jede Installation bekommt
Ordnerdemo/
- besuch.json Beispieldatensätze, nur auf Wunsch
Ordnerfiles/
- hausordnung.pdf ein Dokument, auf das die Beispieldaten verweisen
- MANIFEST.sha256 schreibt das Paketierungsskript
- SIGNATURE schreibt das Paketierungsskript beim Signieren
| Datei | Pflicht | Inhalt |
|---|---|---|
module.json | ja | Das Manifest, beschrieben weiter unten. |
CHANGELOG.md | ja | Im Format Keep a Changelog. Es braucht eine Überschrift ## [<version>] für die Version aus module.json, etwa ## [1.0.0] - 2026-10-12. nexxus zeigt diese Abschnitte der Administration vor jeder Installation und Aktualisierung. |
ui/<name>.html | für eigene Ansichten | Eine abgeschlossene HTML-Datei je eigener Ansicht, CSS und JavaScript eingebettet, höchstens 2 MB. Siehe Ansichten und das View-SDK. |
templates/<name>.html | für eine Mail-Aktion mit PDF | Eine statische HTML-Vorlage, die nexxus füllt und als PDF ausgibt, höchstens 512 KB. Ohne Skripte, und sie lädt nichts nach: siehe Vorlagen. |
data/<fachobjekt>.json | nein | Datensätze, die jede Installation bekommt, etwa ein Anforderungskatalog. |
demo/<fachobjekt>.json | nein | Beispieldatensätze. nexxus legt sie nur an, wenn die Administration bei der ersten Installation Beispieldaten wünscht. |
demo/files/<name> | nein | Dateien, auf die die Beispieldatensätze verweisen. Sie landen unter Dateien. |
MANIFEST.sha256 | nein | Je Datei eine Zeile <sha256> <pfad>, nach Pfad sortiert. Liegt sie bei, muss sie jede andere Datei mit dem richtigen Hash nennen, und nichts sonst. |
SIGNATURE | nein | {"keyId": "...", "alg": "Ed25519", "sig": "..."}, eine Ed25519-Signatur über MANIFEST.sha256. |
MANIFEST.sha256 und SIGNATURE schreibt das Paketierungsskript für Sie. Wie die Datendateien funktionieren, mit Verweisen zwischen Datensätzen und Datumsangaben relativ zum Installationstag, steht unter Daten und Aktualisierungen. Signieren und Hochladen beschreibt Paketieren, installieren und aktualisieren.
Eine hochgeladene Version ändert sich nie mehr. Jede Korrektur, auch ein Tippfehler in einer Ansicht, bekommt eine neue Versionsnummer in einem neuen Ordner.
Vorlagen
Abschnitt betitelt „Vorlagen“Eine Vorlage in templates/ ist das Layout eines Dokuments, das nexxus mit den Werten eines Datensatzes füllt und als PDF ausgibt, heute der Brief einer Mail-Aktion. Der Dateiname besteht aus Buchstaben, Ziffern, - und _, Unterordner gibt es nicht, und eine Vorlage hat höchstens 512 KB.
Der PDF-Dienst würde alles laden, worauf eine Vorlage zeigt. Deshalb enthält eine Vorlage keine Skripte und lädt nichts nach, sonst lehnt nexxus sie beim Hochladen ab: kein <script>, <noscript>, <link>, <iframe>, <frame>, <object>, <embed> oder <applet>, keine Ereignis-Attribute wie onclick, keine javascript:-Adressen und kein CSS-@import. Jede Adresse in src, href und den anderen URL-Attributen sowie in CSS-url() und image-set() muss eine data:-URL oder eine Sprungmarke wie #betreff sein; Links dürfen auch mailto: und tel: sein. Bilder kommen als data:-URL hinein, Schriften sind Systemschriften, und Stile stehen in einem <style> der Vorlage.
Ein Manifest auf das Nötigste beschränkt
Abschnitt betitelt „Ein Manifest auf das Nötigste beschränkt“Das kleinste sinnvolle Modul hat ein Fachobjekt, eine Liste und einen Menüeintrag. Dieses Besucherbuch für den Empfang ist vollständig und gültig:
{ "key": "acme.besucher", "kind": "package", "publisher": "acme", "name": {"de": "Besucherbuch", "en": "Visitor log"}, "description": {"de": "Wer war wann bei uns zu Besuch und warum.", "en": "Who visited us, when and why."}, "icon": "badge", "area": "projekte", "version": "1.0.0", "platform": ">=0.2.0 <1.0.0", "roles": [ {"key": "empfang", "label": {"de": "Empfang", "en": "Reception"}, "permissions": ["besuch.read", "besuch.write"]} ], "entities": [ { "key": "besuch", "label": {"de": "Besuch", "en": "Visit"}, "labelPlural": {"de": "Besuche", "en": "Visits"}, "titleField": "name", "searchFields": ["name"], "fields": [ {"key": "name", "type": "string", "required": true, "label": {"de": "Name", "en": "Name"}}, {"key": "firma", "type": "ref", "target": "core:partner", "label": {"de": "Firma", "en": "Company"}}, {"key": "datum", "type": "date", "required": true, "indexed": true, "label": {"de": "Datum", "en": "Date"}}, {"key": "zweck", "type": "enum", "indexed": true, "default": "termin", "label": {"de": "Zweck", "en": "Purpose"}, "values": [ {"key": "termin", "label": {"de": "Termin", "en": "Meeting"}}, {"key": "lieferung", "label": {"de": "Lieferung", "en": "Delivery"}}, {"key": "wartung", "label": {"de": "Wartung", "en": "Maintenance"}}]} ] } ], "views": [ {"key": "besuch.list", "type": "list", "entity": "besuch", "label": {"de": "Besuche", "en": "Visits"}, "columns": ["datum", "name", "firma", "zweck"], "filters": ["zweck", "datum"], "sort": {"field": "datum", "dir": "desc"}, "create": true} ], "menu": [ {"key": "besuche", "label": {"de": "Besuche", "en": "Visits"}, "icon": "badge", "view": "besuch.list", "order": 1} ]}Nach der Installation hat der Mandant unter Organisation einen Menüabschnitt „Besucherbuch“ mit dem Eintrag „Besuche“. Die Liste hat ein Suchfeld, Filter für Zweck und Datum, eine Schaltfläche zum Anlegen und eine Datensatzseite mit dem Reiter Daten, gebaut aus den vier Feldern. Die Rolle „Empfang“ darf Besuche lesen und schreiben. Eine Ansicht vom Typ record braucht das Modul nicht: Ohne sie zeigt nexxus einfach alle Felder.
Felder auf oberster Ebene
Abschnitt betitelt „Felder auf oberster Ebene“Texte, die Menschen lesen, sind immer ein Objekt mit deutscher und englischer Fassung, {"de": "...", "en": "..."}, dazu wahlweise einer polnischen: {"de": "...", "en": "...", "pl": "..."}. name und description brauchen Deutsch und Englisch, überall sonst genügt eine der beiden. Ein polnischer Text allein zählt nie als eine davon. Jede Person bekommt den Text in ihrer Sprache, sonst auf Englisch, sonst auf Deutsch; wer nexxus auf Polnisch nutzt, sieht bei einem Paket ohne polnische Texte also die englischen. Weitere Schlüssel sind in diesen Objekten nicht erlaubt, und unbekannte Felder lehnt nexxus im ganzen Manifest ab.
| Feld | Typ | Pflicht | Bedeutung | Beispiel |
|---|---|---|---|---|
key | Text | ja | Der Name des Moduls, <herausgeber>.<name>, Kleinbuchstaben, Ziffern und -. Alles andere leitet sich daraus ab. | "acme.besucher" |
kind | Text | ja | Immer "package". | "package" |
publisher | Text | ja | Muss dem Teil von key vor dem Punkt entsprechen. | "acme" |
name | Text, de und en | ja | Steht im Menü, in Seitentiteln und im Katalog. | {"de": "Besucherbuch", "en": "Visitor log"} |
description | Text, de und en | ja | Steht im Katalog. | |
icon | Text | nein | Ein Name aus Material Symbols. | "badge" |
area | Text | ja | Der Fachbereich des Moduls (Tabelle unten). Er ordnet das Modul ein, er schaltet es nicht. | "projekte" |
version | Text | ja | major.minor.patch, ohne Zusatz. | "1.0.0" |
platform | Text | nein | Die nexxus-Versionen, auf denen das Paket läuft, als Versionsbereich. | ">=0.2.0 <1.0.0" |
requires | Objekt | nein | Eingebaute Module, die dieses Paket braucht, jeweils mit Versionsbereich. Nie ein anderes Paket. | {"partners": ">=1.0.0"} |
permissions | Liste | nein | Zusätzliche Rechte neben Lesen und Schreiben je Fachobjekt. | siehe Rechte und Rollen |
roles | Liste | nein | Rollen, die nexxus bei der Installation anlegt. | |
scopes | Liste von Texten | nein | Daten außerhalb des Pakets, die Aktionen und Dienste nutzen dürfen. Braucht die Zustimmung der Administration. | ["core:partner:read"] |
entities | Liste | nein | Die Fachobjekte. | siehe Fachobjekte |
views | Liste | nein | Listen, Datensatzseiten und eigene Ansichten. | siehe Ansichten |
menu | Liste | nein | Menüeinträge. | |
actions | Liste | nein | Arbeitsschritte an Datensätzen. | |
services | Liste | nein | Moduldienste außerhalb von nexxus. | |
settings | Liste | nein | Einstellungen einer Installation, die die Administration pflegt. | |
aiTools | Objekt | nein | Für welche Fachobjekte und Aktionen der KI-Assistent Werkzeuge bekommt. | |
assistant | Objekt | nein | Anweisungen, Beispiel-Anweisungen, Avatare, Skills und KI-Knöpfe für den KI-Assistenten. | |
migrations | Objekt | nein | Schritte, die gespeicherte Datensätze bei einer Aktualisierung umstellen. | |
portal | Objekt | nein | Die öffentliche Seite des Pakets für Kunden ohne Konto. | siehe Kundenportal |
calendar | Liste | nein | Datensätze mit Datum, die der Kalender von nexxus zeigt. | siehe Kalenderquellen |
dashboards | Liste | nein | Bis zu drei Vorlagen für die Startseite, ab nexxus 0.14.0. | siehe Startseiten-Vorlagen |
Versionsbereiche folgen der Schreibweise von npm: Durch Leerzeichen getrennte Vergleiche müssen alle gelten (>=0.2.0 <1.0.0), || trennt Alternativen, ^1.2.0 und ~1.2.0 wirken wie bei npm, * heißt jede Version, und eine einzelne 1.2.3 heißt genau diese Version.
Fachbereiche
Abschnitt betitelt „Fachbereiche“area legt fest, in welchem Menübereich der Abschnitt des Moduls erscheint. Danach richtet sich auch, wo das Modul im Rolleneditor und in der Modulliste steht. Der Bereich ordnet nur ein: Er schaltet das Modul weder ein noch aus.
area | Menübereich in der Leiste |
|---|---|
main, werkzeuge | Start |
zeit, abwesenheit, partner, projekte, rechnungen, kontingente | Organisation |
sicherheit | ISMS |
dokumente | Dokumente |
ai | KI |
teams, system | Administration |
Pakete von vor Oktober 2026 dürfen noch verwaltung nennen. Ein ausgeliefertes Paket ändert sich nicht, deshalb bleibt es gültig und steht weiter unter Organisation. Ein neues Paket nennt einen der Schlüssel aus der Tabelle.
Ein Mandant kann Fachbereiche im Einrichtungsschritt Bereiche abschalten (siehe Erste Einrichtung). Ein Paket betrifft das nicht: Sein Menüabschnitt, seine Seiten, Datensätze, Aktionen und Werkzeuge des KI-Assistenten bleiben für alle mit seinen Rechten, und Verweise auf Kerndaten funktionieren weiter, auch wenn der Bereich abgeschaltet ist, dem diese Daten gehören. Nur Deaktivieren oder Deinstallieren nimmt das Paket weg. Die eine Ausnahme ist eine Aktion vom Typ core-action: Sie führt eine Aktion von nexxus selbst aus, und die Aktionen abgeschalteter Bereiche bietet nexxus nicht an.
Schlüssel, die nexxus ableitet
Abschnitt betitelt „Schlüssel, die nexxus ableitet“Im Manifest schreiben Sie kurze Schlüssel. nexxus macht daraus vollständige Schlüssel, die im Rolleneditor, in den Werkzeugen des KI-Assistenten und in der Schnittstelle auftauchen:
| Was | Vollständiger Schlüssel | Beispiel |
|---|---|---|
| Lese- und Schreibrecht eines Fachobjekts | <modul>.<fachobjekt>.read, <modul>.<fachobjekt>.write | acme.besucher.besuch.read |
| Zusätzliches Recht | <modul>.<recht> | curiaos.kanzlei.freigeben |
| Rolle | <modul>.<rolle> | acme.besucher.empfang |
| Werkzeug des KI-Assistenten für ein Fachobjekt (Suchen, Lesen) | nx_<herausgeber>_<name>_<fachobjekt>_<search oder get> | nx_acme_besucher_besuch_search |
| Aktion des KI-Assistenten für ein Fachobjekt (Anlegen, Ändern) | <modul>:<fachobjekt>.<create oder update> | acme.besucher:besuch.create |
| Aktion des KI-Assistenten für eine Aktion | <modul>:<aktion> | curiaos.kanzlei:frist-berechnen |
| Ereignis eines Fachobjekts | <fachobjekt>.created, .updated, .deleted | besuch.created |
In Werkzeugnamen wird aus - ein _, Aktionsschlüssel behalten ihn. Im Rolleneditor tragen die Rechte eines Pakets deutsche Bezeichnungen: Die Rechte der Fachobjekte bildet nexxus aus der Mehrzahl, also „Besuche lesen“ und „Besuche bearbeiten“, zusätzliche Rechte zeigen ihr label.
Fachobjekte
Abschnitt betitelt „Fachobjekte“Ein Fachobjekt (im Manifest entity) ist ein Geschäftsobjekt. Alle Datensätze aller Pakete liegen in einem gemeinsamen Datensatzspeicher von nexxus; ein Paket legt nie eigene Datenbanktabellen an.
| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
key | Text | ja | Kleinbuchstaben, Ziffern, _ und -. actions und p sind reserviert. |
label | Text | ja | Einzahl, etwa „Besuch“. |
labelPlural | Text | ja | Mehrzahl, etwa „Besuche“. Für Menü, Listen und Rechte. |
icon | Text | nein | Name aus Material Symbols, erscheint am Reiter Daten. |
titleField | Text | nein | Das Feld, das einen Datensatz in Kopfzeilen, Links und Auswahlfeldern benennt. Fehlt es, heißt ein Datensatz #<id>. |
searchFields | Liste | nein | Felder vom Typ string, text, enum oder ref, in denen das Suchfeld sucht. Ein ref-Feld findet den Datensatz über das, worauf es zeigt: einen Datensatz des Pakets über dessen eigene searchFields, Kerndaten über ihren Namen, etwa eine Beteiligung über den Namen des Kontakts. |
journal | Wahrheitswert | nein | Das Fachobjekt hält fest, was geschehen ist, etwa wer an einem Auftrag was getan hat. Personen legen keine Einträge an, ändern und löschen keine, auch Administratoren nicht. Das Paket schreibt sie über seinen Moduldienst, auch für eine Person, die das Fachobjekt nur lesen darf, oder über eine create-Aktion. Listen und KI-Assistent bieten dort kein Neu, Bearbeiten oder Löschen an, und ein Datenimport schreibt nie hinein. Eine Aktion vom Typ update oder dictate, eine core-action mit store und eine mail mit set dürfen nicht darauf zielen. Ein älteres nexxus lehnt ein Paket mit journal beim Hochladen ab. |
fields | Liste | ja | Mindestens ein Feld. |
Feldtypen
Abschnitt betitelt „Feldtypen“type | Gespeichert als | Optionen und Hinweise |
|---|---|---|
string | Text, ohne Leerzeichen am Rand | maxLength, Standard 500. |
text | Text | Langer Text, als Textbereich. maxLength Standard 20 000. Nicht indizierbar. |
number | Zahl | min, max, format: integer, decimal oder currency (Anzeige in Euro oder in der Währung, die currency nennt). |
boolean | true oder false | Als Schalter. |
date | "JJJJ-MM-TT" | |
datetime | ISO 8601 mit Zeitzone, gespeichert in UTC | |
enum | der Schlüssel eines Werts | values: Liste von {"key", "label"}, mindestens einer. multiple: true speichert eine Liste. |
ref | die Id eines Datensatzes | target (siehe unten). multiple: true speichert eine Liste. |
json | beliebiges JSON | Nicht indiziert, nicht im erzeugten Formular. |
geo | {"lat": -33.92, "lng": 18.42}, Grad in WGS84, sieben Nachkommastellen | Ein Ort: lat von -90 bis 90, lng von -180 bis 180. Die Datensatzseite zeigt ihn als Nadel auf einer kleinen Karte, im Formular lässt er sich eintippen, über eine Adresse suchen oder durch Verschieben der Nadel setzen. geocodeFrom füllt ihn aus Adressfeldern. Nicht indizierbar, nie in einem öffentlichen Formular. |
Ein ref zeigt auf:
- ein Fachobjekt desselben Pakets:
"target": "besuch", oder - einen Datensatz von nexxus:
core:partner(eine Firma oder Person unter Kontakte),core:partnerContact(ein Ansprechpartner eines Partners),core:user,core:dmsDocument(ein Dokument unter Dateien),core:ismsMeasure,core:project,core:invoiceodercore:opportunity(ein mögliches Geschäft unter Vertrieb).
Ein Verweis zeigt nie auf ein Fachobjekt eines anderen Pakets (siehe Pakete sind in sich geschlossen).
Wählt jemand einen Wert, prüft nexxus, ob der Datensatz im Mandanten existiert, und bei Dokumenten, ob die Person es lesen darf. Die Auswahl für Partner, Ansprechpartner, Projekte, Rechnungen und ISMS-Maßnahmen steht allen offen, die diese Liste in nexxus sehen dürfen; bei Ansprechpartnern stehen nur aktive zur Wahl, und eine Rechnung ohne Nummer erscheint als #<id>. Benutzer und Dokumente stehen allen im Mandanten offen, Dokumente nur die, die die Person lesen darf.
Feldoptionen
Abschnitt betitelt „Feldoptionen“| Option | Typ | Bedeutung |
|---|---|---|
key | Text | Pflicht. Kleinbuchstaben, Ziffern, _ und -. |
label, hint | Text | Feldbezeichnung und Hilfetext im Formular. |
required | Wahrheitswert | Braucht einen Wert. |
default | Wert | Wird beim Anlegen gesetzt, wenn das Feld leer ist. Muss zum Typ passen. |
readonly | Wahrheitswert | Nur Aktionen, Moduldienste, Migrationen und Datendateien dürfen den Wert setzen. Das Formular einer Person zeigt ihn, ändert ihn aber nicht. |
readonlyWhen | Bedingung | Sperrt das Feld für Personen, sobald der gespeicherte Datensatz eine Bedingung erfüllt, in der Form des when einer Aktion, etwa { "field": "status", "in": ["zugeordnet", "verworfen"] }. Siehe unten. |
indexed | Wahrheitswert | Macht das Feld filter- und sortierbar. Nicht für text, json und geo. |
unique | Wahrheitswert | Zwei Datensätze dürfen den Wert nicht teilen (verglichen ohne Leerzeichen am Rand und ohne Groß- und Kleinschreibung). Nur für string und number, schließt indexed ein. |
sequence | Text | Nur für string: ein Nummernformat wie "{yyyy}/{n:4}". nexxus vergibt beim Anlegen die nächste Nummer. Schließt unique und readonly ein. |
geocodeFrom | Liste | Nur für geo: Felder desselben Fachobjekts vom Typ string, text oder enum, etwa ["address", "city", "country"]. Siehe unten. |
currency | Text | Nur für eine number mit format: currency: der ISO-4217-Code in Großbuchstaben, etwa "ZAR". Listen und Datensatzseiten zeigen den Betrag mit dem Währungssymbol in der Sprache der Person, etwa 12.500,00 R oder R 12,500.00. Ohne Angabe ist der Betrag in Euro (12.500,00 €, €12,500.00). Ein unbekannter Code wird beim Hochladen abgelehnt. |
group | Text | Abschnitt im erzeugten Formular, wenn die Datensatzansicht keine sections hat. |
maxLength, min, max, format | Siehe die Typen oben. | |
values, multiple | Für enum (multiple auch für ref). unique und multiple schließen sich aus. | |
target | Text | Für ref. |
accept | Liste | MIME-Typen für Dokumentverweise, etwa ["image/*"]. Geprüft für Dateien, die eine Ansicht in das Feld hochlädt (nx.files.upload), nicht wenn eine Person ein vorhandenes Dokument auswählt. Nur Bildtypen lassen die Datensatzseite die Dokumente als Bilder zeigen (siehe unten). |
Die Währung ist eine Beschriftung, keine Umrechnung: Ändert eine neue Version currency, stehen die gespeicherten Beträge danach in der neuen Währung. E-Mail-Texte und Vorlagen bekommen die Zahl ohne Symbol (12.500,00, auf Englisch 12,500.00, auf Polnisch 12 500,00) und schreiben die Währung selbst.
Mit geocodeFrom ermittelt nexxus die Koordinaten selbst. Wird ein Datensatz angelegt oder ändert sich eines der genannten Felder, und ist der Ort leer oder die Adresse eine andere, setzt nexxus den Text dieser Felder zusammen (eine Auswahlliste mit ihrer Bezeichnung), sucht ihn in der Adresssuche der Installation und speichert den ersten Treffer. Setzt dieselbe Anfrage den Ort selbst, gilt dieser Wert. Findet die Suche nichts, scheitert sie oder dauert sie länger als drei Sekunden, wird trotzdem gespeichert, und der Ort bleibt, wie er war. Daten- und Beispieldateien bringen ihre Koordinaten selbst mit; beim Installieren wird nichts gesucht.
Im Formular fragt Adresse suchen über der Karte ab dem dritten Zeichen dieselbe Adresssuche und bietet bis zu fünf Treffer an. Ein gewählter Treffer setzt die Nadel und zeigt sie auf Straßenebene. Die Suche läuft auf dem Server, der Browser fragt also keinen Dritten, und jede Person hat 20 Suchen pro Minute.
Ein ref auf core:dmsDocument, dessen accept nur Bildtypen nennt, zeigt seine Dokumente auf der Datensatzseite als Vorschaubilder. Ein Klick öffnet ein Bild groß, mit den Pfeiltasten blättern Sie durch die übrigen. Beim Bearbeiten stehen die Vorschaubilder unter der Auswahl. Ohne accept macht nexxus das ebenso, wenn jeder gewählte Dateiname eine Bildendung hat (.jpg, .png, .webp und ähnliche).
readonlyWhen sperrt ein Feld, sobald ein Datensatz einen bestimmten Stand erreicht hat, etwa Absender, Eingangsdatum und Betreff eines Schreibens, nachdem es einer Akte zugeordnet wurde:
{ "key": "absender", "type": "string", "readonlyWhen": { "field": "status", "in": ["zugeordnet", "verworfen"] } }Die Bedingung nennt Felder desselben Fachobjekts, wie das when einer Aktion (auch set und all), und jeder Wert in in muss ein Schlüssel dieser Auswahlliste sein. nexxus prüft sie am gespeicherten Datensatz, nicht an der Änderung, die gerade gespeichert wird: Ein neuer Datensatz ist nie gesperrt, und das Speichern, das den Status setzt, darf die Felder, die er sperrt, noch mitbringen. Ist das Feld gesperrt, zeigt die Datensatzseite es schreibgeschützt mit einem Hinweis, und eine Änderung durch eine Person lehnt der Server mit einer Meldung ab, die den Grund nennt, etwa Absender ist gesperrt: Status ist „Zugeordnet“. Den unveränderten Wert mitzuspeichern geht. Aktionen, Moduldienste und Migrationen schreiben das Feld weiterhin, und der KI-Assistent kennt die Sperre. Ein Diktat-Knopf (type: dictate) auf ein gesperrtes Feld wird nicht angeboten, und im Kalender lässt sich ein Datensatz, dessen Beginn- oder Endfeld gesperrt ist, weder ziehen noch in der Länge ändern noch per Tastatur verschieben. readonlyWhen steht nicht neben readonly, nicht an einem sequence-Feld und nicht in settings. Ein Paket mit readonlyWhen braucht nexxus 0.14.0 oder neuer und gibt deshalb "platform": ">=0.14.0 <1.0.0" an.
Jedes ref ist automatisch indiziert. Ein sequence-Format kennt die Platzhalter {yyyy}, {yy} und genau ein {n} oder {n:<stellen>}; aus "GER-{n:4}" wird etwa GER-0001. Steht ein Jahr im Format, beginnt der Zähler jedes Jahr neu. Er zählt je Mandant, Modul, Fachobjekt und Feld.
Ansichten
Abschnitt betitelt „Ansichten“Ansichten bestimmen, was die Mitarbeitenden sehen. Es gibt fünf Typen:
type | Gezeichnet von | Was es ist |
|---|---|---|
list | nexxus | Die Liste eines Fachobjekts: entity, columns, filters, sort, create. |
record | nexxus | Der Reiter Daten eines Datensatzes: entity, sections, related, actions. |
related | nexxus | Ein Reiter mit Datensätzen, die auf einen anderen Datensatz verweisen, etwa auf einen Partner. |
custom | Ihr HTML, in der Sandbox | Eine Seite, ein Reiter, ein Dialog oder eine Kachel: slot, html, target, when. |
map | nexxus | Die Datensätze eines Fachobjekts auf einer Karte: entity, geoField, titleField, subtitleFields, colorField, imageField. |
| Feld | Genutzt von | Bedeutung |
|---|---|---|
key | allen | Pflicht. Kleinbuchstaben, Ziffern, _, . und -, etwa besuch.list. |
type | allen | Pflicht. |
label, icon | allen | Bezeichnung für Reiter, Menü und Dialog. |
entity | list, record, related, map | Das gezeigte Fachobjekt. |
columns | list, related | Feldschlüssel oder {"field": "status", "display": "badge"}. |
filters | list | Indizierte Felder, die als Filter angeboten werden. Auswahllisten werden zur Mehrfachauswahl, Verweise zum Auswahlfeld, Datumsfelder zum Zeitraum. |
sort | list | {"field": "<indiziertes feld>", "dir": "asc"} oder "desc". Ohne Angabe steht der neueste Datensatz oben. |
create | list | true zeigt die Schaltfläche zum Anlegen. |
sections | record | [{"key", "label", "fields": [...]}]. Nur die genannten Felder stehen auf dem Reiter Daten. |
related | record | [{"entity", "via", "label", "columns"}]: Listen der Datensätze, deren Verweisfeld via hierher zeigt. |
actions | record | Genau die Aktionen und Dialoge, die die Aktionsleiste des Datensatzes anbietet, in dieser Reihenfolge. |
slot, target | related, custom | Wo die Ansicht erscheint und an welchem Fachobjekt. |
via | related | Das Verweisfeld, das auf das Ziel zeigt. |
html | custom | ui/<name>.html. |
when | custom | Zeigt die Ansicht nur, wenn ein Feld bestimmte Werte hat. |
size | custom | s, m oder l für Kacheln auf dem Dashboard. |
geoField | map | Pflicht. Das geo-Feld, das einen Datensatz auf die Karte setzt. Datensätze ohne Wert fehlen auf der Karte. |
titleField | map | Titel der Markierung. Ohne Angabe das titleField des Fachobjekts. |
subtitleFields | map | Felder unter dem Titel. |
colorField | map | Ein Feld vom Typ string, enum, boolean oder ein einfacher ref: Jeder Wert bekommt eine eigene Farbe. |
imageField | map | Ein ref auf core:dmsDocument: Sein erstes Bild steht in der Liste neben der Karte vor dem Datensatz. |
Eine Karte der Ferienhäuser, eingefärbt nach Region, mit einem Foto je Haus in der Liste, als eigener Menüeintrag:
{"key": "listing.map", "type": "map", "entity": "listing", "label": {"de": "Karte", "en": "Map"}, "geoField": "location", "titleField": "name", "subtitleFields": ["city", "kind"], "colorField": "region", "imageField": "photos"}nexxus zeichnet die Karte selbst, nicht in der Sandbox einer eigenen Ansicht, und ein Klick auf eine Markierung öffnet den Datensatz. Die Liste neben der Karte zeigt die Datensätze im sichtbaren Ausschnitt: Verschieben oder Zoomen grenzt sie ein, Alle zeigen holt alle Markierungen zurück ins Bild. Die Karte kommt vom Server der Installation; ist dort keine eingerichtet, zeigt die Ansicht einen Platzhalter.
Um die Karte auf einem Datensatz zu öffnen, ruft eine eigene Ansicht nx.nav.open({view: 'listing.map', id}) auf; das öffnet /x/<modul>/p/<ansicht>?focus=<id>. Die Karte zeigt dann diesen Datensatz auf Straßenebene, seine Markierung ist gewählt und seine Zeile in der Liste hervorgehoben. Hat der Datensatz keinen Ort oder gibt es ihn nicht, zeigt die Karte alle Datensätze wie ohne focus.
Ohne list-Ansicht baut nexxus die Liste aus dem Titelfeld und den ersten vier indizierten Feldern. Ohne record-Ansicht zeigt der Reiter Daten alle Felder außer json, gruppiert nach group. Slots, den Aufbau der Datensatzseite und das SDK für eigene Ansichten beschreibt Ansichten und das View-SDK.
| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
key | Text | ja | |
label | Text | ja | Bezeichnung des Eintrags. |
icon | Text | nein | Name aus Material Symbols. |
view | Text | ja | Eine Ansicht vom Typ list oder map oder eine eigene Ansicht im Slot page. |
order | Zahl | nein | Position im Abschnitt des Moduls. |
Die Einträge stehen in einem Abschnitt, der so heißt wie das Modul, hinter den eingebauten Abschnitten des Menübereichs. Eine Person sieht einen Eintrag nur, wenn sie das zugehörige Fachobjekt lesen darf; für eine Seite genügt irgendein Recht des Moduls.
Aktionen
Abschnitt betitelt „Aktionen“Aktionen sind die Arbeitsschritte eines Moduls: ein Gerät verleihen, einen Entwurf freigeben, eine Frist erledigen.
{ "key": "zurueckgeben", "entity": "geraet", "type": "update", "label": {"de": "Zurückgeben", "en": "Return"}, "icon": "assignment_return", "permission": "verleihen", "when": {"field": "status", "in": ["verliehen"]}, "confirm": {"de": "Gerät als zurückgegeben buchen?", "en": "Book the device as returned?"}, "set": {"status": "verfuegbar", "ausgeliehen_an": null, "rueckgabe_am": null}, "aiTool": true}| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
key | Text | ja | |
label | Text | ja | Beschriftung der Schaltfläche. |
type | Text | ja | update, create, core-action, service oder mail. |
entity | Text | ja | Das Fachobjekt, an dem die Aktion läuft. Ohne existierendes Fachobjekt lehnt nexxus die Aktion ab. |
icon | Text | nein | Name aus Material Symbols. |
set | Objekt | bei update | Feldwerte, die am Datensatz gesetzt werden. |
creates, values | Text, Objekt | bei create | Das Fachobjekt, das entsteht, und seine Feldwerte. |
coreAction, scope, input, store | bei core-action | Führt eine Aktion von nexxus selbst aus: partner.create, partnerContact.create, opportunity.create, ismsMeasure.create oder invoice.createDraft. scope muss unter scopes stehen. input geht unverändert weiter, store: {"field", "from": "id"} schreibt das Ergebnis in ein Feld. | |
service, operation, async | bei service | Ruft eine Operation eines Moduldienstes auf. Mit async: true antwortet nexxus, sobald der Dienst den Aufruf angenommen hat. | |
mail, log, onSent, onFailed | bei mail | Versendet den Datensatz per E-Mail, siehe Mail-Aktionen. | |
permission | Text | nein | Ein zusätzliches Recht des Moduls. Ohne Angabe braucht die Aktion das Schreibrecht des Fachobjekts. |
when | Objekt | nein | Die Aktion wird nur angeboten und ausgeführt, wenn die Bedingung gilt. |
confirm | Text | nein | Diese Frage stellt nexxus, bevor die Aktion läuft. |
aiTool | Wahrheitswert | nein | Der KI-Assistent darf diese Aktion im Chat vorschlagen. |
distinct | Liste | nein | Mindestens zwei Felder (oder $user), deren Werte sich unterscheiden müssen, etwa ["geprueft_von", "$user"]: Wer prüft, darf nicht die Person sein, die die Aktion ausführt. |
Werte in set, values und input dürfen Platzhalter enthalten. Ein Wert, der genau aus einem Platzhalter besteht, behält seinen Typ: "{{record.id}}" bleibt eine Zahl.
| Platzhalter | Wird zu |
|---|---|
{{record.<feld>}} | Ein Feld des Datensatzes, an dem die Aktion läuft. |
{{record.<verweis>.<feld>}} | Ein Feld des Datensatzes dieses Pakets, auf den der Verweis zeigt (eine Ebene). |
{{record.id}} | Die Id des Datensatzes. |
{{payload.<schlüssel>}} | Ein Wert, den der Aufrufer mitgeschickt hat. Fehlt der Schlüssel, lässt update das Feld unverändert und create lässt es leer; ein ausdrückliches null leert es. |
{{user.id}} | Die Person, die die Aktion ausführt. |
$today, $now | Das heutige Datum, die aktuelle Uhrzeit. |
when nimmt {"field": "<schlüssel>", "in": [werte]} oder {"field": "<schlüssel>", "set": true} (oder false). Mehrere Bedingungen, die alle gelten müssen, stehen als Liste unter all: {"all": [{"field": "status", "in": ["freigegeben"]}, {"field": "kanal", "in": ["email"]}]}. In all stehen nur einzelne Bedingungen, kein zweites all. Dieselben Formen gelten überall, wo ein Manifest when kennt, auch an Ansichten. nexxus blendet eine Aktion aus, deren Bedingung nicht gilt, und prüft sie vor dem Ausführen noch einmal auf dem Server.
Aktionen, die Eingaben brauchen (eine Person, ein Datum), gehören nicht in die Aktionsleiste des Datensatzes. Schreiben Sie dafür einen Dialog, der die Aktion mit diesen Eingaben aufruft. Ihr erstes Modul zeigt dieses Muster.
Mail-Aktionen
Abschnitt betitelt „Mail-Aktionen“Eine Aktion vom Typ mail versendet einen Datensatz per E-Mail an einen Kontakt, auf Wunsch mit einem Brief als PDF: etwa einen freigegebenen Entwurf an die Versicherung. Versenden kann sie immer nur ein Mensch, nachdem er eine Vorschau gesehen hat. Die Adresse ermittelt nexxus selbst, legt das PDF unter Dateien ab, hält den Versand in einem Fachobjekt des Pakets fest und trägt später ein, ob die Mail hinausging.
{ "key": "entwurf-versenden", "entity": "entwurf", "type": "mail", "label": {"de": "Versenden", "en": "Send"}, "icon": "send", "permission": "versenden", "scope": "core:mail:send", "when": {"all": [{"field": "status", "in": ["freigegeben", "versand_fehlgeschlagen"]}, {"field": "kanal", "in": ["email"]}]}, "mail": { "to": "empfaenger", "subject": "{{record.betreff}}", "textSetting": "versand_mailtext", "replyToSetting": "versand_antwort_an", "locale": "de", "pdf": {"template": "templates/brief.html", "fileName": "{{record.akte.aktennummer}} {{record.betreff}}.pdf", "folder": ["Kanzlei", "Schriftverkehr", "{{record.akte.aktennummer}}"]} }, "log": {"entity": "versand", "values": {"akte": "{{record.akte}}", "entwurf": "{{record.id}}", "betreff": "{{record.betreff}}"}}, "set": {"status": "im_versand"}, "onSent": {"set": {"status": "versendet", "versendet_am": "$now"}}, "onFailed": {"set": {"status": "versand_fehlgeschlagen"}}}| Feld | Pflicht | Bedeutung |
|---|---|---|
scope | ja | Immer core:mail:send, und der Scope steht unter scopes. |
mail.to | ja | Ein Verweisfeld des Fachobjekts der Aktion mit dem Ziel core:partner, nicht multiple. Die Mail geht an die E-Mail-Adresse dieses Kontakts. |
mail.subject | ja | Der Betreff, mit Platzhaltern. Eine Zeile. |
mail.textSetting | ja | Eine Einstellung vom Typ text oder string mit dem Mailtext, Klartext mit Platzhaltern. Die Administration kann ihn auf der Paketseite ändern. nexxus versendet ihn im eigenen Mail-Layout als Schreiben der Organisation, mit ihrem Namen oder Logo; Leerzeilen trennen die Absätze. |
mail.replyToSetting | nein | Eine Einstellung vom Typ string mit der Adresse, an die Antworten gehen. |
mail.locale | nein | de (Standard), en oder pl: die Sprache, in der die Werte in Betreff, Mailtext, Dateiname und Ordnern stehen. |
mail.pdf.template | für ein PDF | Eine Vorlage des Pakets, templates/<name>.html oder nur <name>. |
mail.pdf.fileName | für ein PDF | Der Name des PDFs, mit Platzhaltern. nexxus ersetzt Zeichen, die in Dateinamen nicht stehen dürfen, etwa /, : und ?, durch -, behält höchstens 200 Zeichen und lässt den Namen auf .pdf enden. |
mail.pdf.folder | für ein PDF | Der Ordnerpfad unter Dateien, ein Name je Eintrag, mit Platzhaltern. Ein / in einem Wert wird zu -, aus 2026/0184 wird also der Ordner 2026-0184. |
log.entity | ja | Das Fachobjekt des Pakets, das jeden Versand festhält. |
log.values | nein | Weitere Werte für den Versand-Datensatz, mit den Platzhaltern von create. |
set | nein | Wird am Datensatz gesetzt, sobald die Mail in der Warteschlange steht. |
onSent.set, onFailed.set | nein | Wird am Datensatz gesetzt, wenn die Mail hinausging oder endgültig scheiterte. $now und Platzhalter des Datensatzes gehen. |
Die Adresse kommt aus dem Kontakt. nexxus liest sie aus dem Kontakt in mail.to, nie aus dem Aufruf oder einer Vorlage. Wer versendet, braucht das Recht, Kontakte zu lesen (partner.partner.read, die Administration hat es), denn die Vorschau zeigt die Adresse. Hat der Kontakt keine E-Mail-Adresse, bricht die Aktion ab mit „Der Empfänger hat keine E-Mail-Adresse. Bitte im Kontakt ergänzen.“
Platzhalter. Betreff, Mailtext, Dateiname, Ordnernamen und die Vorlage kennen diese:
| Platzhalter | Wird zu |
|---|---|
{{record.<feld>}}, {{record.id}} | Ein Feld des Datensatzes, so wie seine Seite es zeigt: Verweise mit Namen, Auswahlwerte mit ihrer Beschriftung, Datumsangaben als 14.10.2026. |
{{record.<verweis>.<feld>}} | Ein Feld des Datensatzes dieses Pakets, auf den der Verweis zeigt (eine Ebene). |
{{empfaenger.name}} | Der Name des Kontakts. |
{{empfaenger.anschrift}} | Die Postanschrift des Kontakts, eine Zeile je Zeile der Anschrift. |
{{settings.<schlüssel>}} | Eine Einstellung der Installation. |
{{heute}} | Das heutige Datum, TT.MM.JJJJ. |
Sprache der Werte. Ohne mail.locale stehen die Werte auf Deutsch: 14.10.2026, 1.252,50, die deutsche Beschriftung, „Ja“. Mit "locale": "en" stehen sie auf Englisch, wie in britischen Abrechnungszeilen: 14/10/2026, 1,252.50, die englische Beschriftung, „Yes“. Mit "locale": "pl" stehen sie auf Polnisch: 14.10.2026, 1 252,50 (ein geschütztes Leerzeichen zwischen den Tausendern), die polnische Beschriftung, „Tak“. Fehlt eine Beschriftung in dieser Sprache, nimmt nexxus die englische, sonst die deutsche. Die Vorlage richtet sich nicht nach mail.locale, sondern nach ihrem eigenen <html lang>: lang="en" (oder en-GB, en-ZA) ist Englisch, lang="pl" (oder pl-PL) Polnisch, alles andere Deutsch. Beträge bekommen nie ein Währungszeichen; das schreiben Text oder Vorlage selbst (R {{record.payout}}). Uhrzeiten gelten in Europe/Berlin.
{{payload.<schlüssel>}} gibt es hier nicht: Was hinausgeht, ist genau das, was die Person in der Vorschau gesehen hat. In der Vorlage wird jeder Wert maskiert, Zeilenumbrüche werden zu <br>. Ein Platzhalter, der nichts benennt, bleibt so stehen, wie er geschrieben ist; ein Tippfehler im Mailtext fällt also in der Vorschau auf.
Vorschau und Bestätigung. Vor dem Versand holt die Ansicht eine Vorschau: Name und Adresse des Empfängers, Betreff, Mailtext und den Dateinamen des PDFs. Sie schreibt nichts und kommt mit einem Token, das zehn Minuten gilt. Der Versand bringt dieses Token mit; nexxus stellt die Mail neu zusammen und versendet nur, wenn sie genau dem entspricht, wofür das Token ausgestellt wurde: dieselbe Person, derselbe Datensatz in derselben Version, dieselbe Adresse, derselbe Betreff, Text und Dateiname. Ein Token versendet einmal. Die Aufrufe beschreibt Ansichten und das View-SDK.
Was beim Versand geschieht, in einem Schritt, der ganz gelingt oder nichts hinterlässt:
PDF
nexxus füllt die Vorlage und lässt sie ohne JavaScript als PDF erzeugen.
Dateien
Das PDF landet im Ordnerpfad, dort, wo die Person bearbeiten darf.
Warteschlange
Die Mail kommt mit dem PDF als Anhang und der Antwortadresse in die Warteschlange.
Datensatz
Ein Versand-Datensatz mit Status in_warteschlange, dann set am Datensatz.
Der Ordnerpfad beginnt bei einem obersten Ordner dieses Namens, den die Person sieht, einem eigenen oder einem, der für sie freigegeben ist. Einen Ordner „Kanzlei“, den die Administration anlegt und für das Team freigibt, nutzen deshalb alle, die versenden, und seine Rechte entscheiden, wer die Schreiben sieht. Ist für die Person kein solcher Ordner freigegeben, beginnt der Pfad in ihren eigenen Dateien. Fehlende Ordner legt nexxus nur dort an, wo die Person bearbeiten darf, und sie braucht das Recht zum Bearbeiten für den Ordner, in dem das PDF landet.
Die Warteschlange versendet innerhalb einer Minute. Ging die Mail hinaus, setzt nexxus den Versand-Datensatz auf versendet mit message_id und versendet_am und wendet onSent an; scheiterte sie endgültig, auf fehlgeschlagen mit fehler und wendet onFailed an. „Versendet“ heißt, dass der Mailserver die Mail angenommen hat, nicht dass sie angekommen ist. Wer nach einem Fehlschlag erneut versendet, bekommt einen neuen Versand-Datensatz; der alte bleibt. Die Mails stehen außerdem unter BetriebWartungE-Mail-Versand. Wer eine solche Mail dort weiterleitet, braucht das Recht, das PDF unter Dateien zu öffnen.
Das Fachobjekt für den Versand braucht diese Felder, die nexxus selbst füllt; log.values darf sie nicht nennen:
| Feld | Typ | Gefüllt |
|---|---|---|
adresse | string | die Adresse, an die die Mail ging |
dokument | ref auf core:dmsDocument | das PDF, wenn die Aktion eines hat |
status | enum mit mindestens in_warteschlange, versendet, fehlgeschlagen | wie oben beschrieben |
message_id | string | nach dem Versand |
fehler | text | nach einem endgültigen Fehlschlag |
ausgeloest_von | ref auf core:user | die Person, die versendet hat |
ausgeloest_am, versendet_am | datetime | wann die Person versendet hat, wann die Mail hinausging |
Die Versand-Datensätze halten fest, was die Kanzlei verlassen hat, deshalb ändert sie niemand nachträglich: Jedes Feld der Tabelle und aus log.values muss readonly sein, keine andere update-Aktion des Pakets darf auf das Versand-Fachobjekt zielen, und Anlegen oder Löschen eines Versand-Datensatzes über die Datensatz-API lehnt nexxus ab (409 MAIL_LOG). Eine andere create-Aktion darf einen Eintrag anlegen, den die Mail-Warteschlange nicht kennen kann, etwa einen Brief, den jemand per Post verschickt hat. message_id und fehler setzt sie nicht: Die meldet nur die Warteschlange. Die Seiten bieten dafür keine Schaltfläche an, und der KI-Assistent kann die Versand-Datensätze lesen, aber nicht schreiben. Sie verschwinden nur, wenn das Paket samt Daten deinstalliert wird.
Der PDF-Dienst. Das PDF entsteht beim gehosteten PDF-Dienst, der Brief verlässt dafür kurz den Server. Ob das erlaubt ist, entscheidet der Betreiber einer Instanz (Einstellung nexxus.modules.mail.allow-hosted-pdf-renderer); ohne diese Freigabe brechen Vorschau und Versand einer Aktion mit pdf mit einer Meldung ab, die die Einstellung nennt.
Eine Mail-Aktion ist nie ein Werkzeug des KI-Assistenten: "aiTool": true und einen Eintrag unter aiTools.actions lehnt nexxus beim Hochladen ab. Der KI-Assistent kann den Entwurf schreiben, versenden tut ihn ein Mensch.
Rechte und Rollen
Abschnitt betitelt „Rechte und Rollen“"permissions": [ {"key": "verleihen", "label": {"de": "Geräte verleihen", "en": "Lend equipment"}, "description": {"de": "Darf Geräte ausleihen und zurücknehmen.", "en": "May lend equipment and take it back."}}],"roles": [ {"key": "verwaltung", "label": {"de": "Geräteverwaltung", "en": "Equipment management"}, "permissions": ["geraet.read", "geraet.write", "verleihen"]}]- Jedes Fachobjekt hat ein Lese- und ein Schreibrecht, ohne dass Sie es deklarieren.
permissionsergänzt weitere (keyundlabelsind Pflicht). Ein Recht darf nicht so heißen wie ein Fachobjekt. - Eine Rolle nennt kurze Schlüssel:
<fachobjekt>.read,<fachobjekt>.writeoder ein zusätzliches Recht. - Bei der Installation legt nexxus je Rolle im Manifest eine Berechtigungsrolle an, und die Administration kann ihr im Installationsdialog gleich Personen zuweisen. Sonst bekommt niemand automatisch eine Rolle. Spätere Zuweisungen erledigen Sie im Rolleneditor, siehe Rollen und Rechte.
Scopes nennen die Daten außerhalb des Pakets, die seine Aktionen und Moduldienste nutzen dürfen. Eigene Ansichten brauchen keine Scopes: Sie handeln immer mit den Rechten der Person, die gerade hinsieht.
| Scope | Erlaubt |
|---|---|
core:partner:read | Partner lesen |
core:partner:create | Kontakte anlegen, über eine core-action partner.create |
core:user:read | Benutzer lesen |
core:dmsDocument:read | Dokumente unter Dateien lesen |
core:dmsDocument:create | Dateien, die eine Ansicht hochlädt (nx.files.upload, etwa Fotos vom Tablet), als Dokumente unter Dateien ablegen, siehe Ansichten |
core:ismsMeasure:read, core:ismsMeasure:create | ISMS-Maßnahmen lesen und anlegen |
core:partnerContact:read | Ansprechpartner lesen |
core:partnerContact:create | Ansprechpartner eines Kontakts anlegen, über eine core-action partnerContact.create; Aktiv und ISMS-Empfänger setzt nexxus selbst, wenn sie fehlen |
core:project:read | Projekte lesen |
core:invoice:read | Rechnungen lesen |
core:invoice:create | Rechnungsentwürfe mit einer Position anlegen, über eine core-action invoice.createDraft; stellen tut sie eine Person |
core:opportunity:read | Opportunities lesen |
core:opportunity:create | Opportunities anlegen, über eine core-action opportunity.create; ohne Angabe bekommt sie die erste offene Stufe und EUR. Braucht den Bereich Vertrieb |
core:mail:send | E-Mails im Namen Ihrer Organisation versenden, über eine Mail-Aktion |
Jedem Scope stimmt die Administration im Installationsdialog zu. Bringt eine Aktualisierung neue Scopes, fragt nexxus erneut. Scopes auf Datensätze eines anderen Pakets (pkg:<paket>:<fachobjekt>:read oder :write) gibt es nicht mehr; nexxus lehnt sie beim Hochladen ab.
Dienste
Abschnitt betitelt „Dienste“"services": [ {"key": "rechner", "label": {"de": "Rechner", "en": "Calculator"}, "url": "http://localhost:8390/rechner", "events": ["besuch.created"], "operations": ["pruefen"]}]| Feld | Typ | Pflicht | Bedeutung |
|---|---|---|---|
key | Text | ja | |
label | Text | ja | Steht bei der Zustimmung und in Fehlermeldungen. |
description | Text | nein | |
url | Text | ja | http:// oder https://. Der Betreiber der Instanz kann den Dienst woanders hin zeigen lassen. |
events | Liste | nein | Änderungen, die der Dienst bekommt: <fachobjekt>.created, .updated, .deleted der Fachobjekte des eigenen Pakets. |
operations | Liste | nein | Operationen, die Aktionen vom Typ service aufrufen dürfen. |
Wie Dienste aufgerufen, angemeldet und betrieben werden, steht unter Moduldienste.
Einstellungen
Abschnitt betitelt „Einstellungen“settings hat dieselbe Form wie die Felder eines Fachobjekts, nur ohne ref. Die Administration pflegt die Werte auf der Paketseite, und Moduldienste bekommen sie bei jedem Aufruf mit.
"settings": [ {"key": "vorfrist_werktage", "type": "number", "format": "integer", "min": 1, "max": 10, "default": 3, "required": true, "label": {"de": "Vorfrist (Werktage vor Fristende)", "en": "Early warning (working days before the deadline)"}}]KI-Assistent: aiTools und assistant
Abschnitt betitelt „KI-Assistent: aiTools und assistant“"aiTools": {"entities": ["geraet"], "actions": ["frist-berechnen"]},"assistant": { "instructions": {"de": "Du hilfst bei den Leihgeräten des Teams. ...", "en": "You help with the team's loan equipment. ..."}, "prompts": [ {"view": "geraet.list", "label": {"de": "Wer hat welches Gerät?", "en": "Who has which device?"}, "prompt": {"de": "Liste alle verliehenen Geräte mit Person und Rückgabedatum.", "en": "List every device on loan with person and due date."}} ], "avatars": [ {"key": "greta", "name": "Greta", "role": {"de": "Geräteausgabe", "en": "Equipment desk"}, "gradient": "teal", "persona": {"de": "Nennt immer das Rückgabedatum. ...", "en": "Always names the due date. ..."}, "greeting": {"de": "Welches Gerät suchen Sie?", "en": "Which device are you looking for?"}, "expertise": {"de": ["Ausleihe", "Rückgabe"], "en": ["Loans", "Returns"]}} ], "skills": [ {"key": "rueckgabe-erinnern", "avatar": "greta", "name": {"de": "An Rückgabe erinnern", "en": "Remind about return"}, "description": {"de": "Entwirft eine Erinnerung an die Person, die das Gerät hat.", "en": "Drafts a reminder to the person who has the device."}, "instructions": {"de": "Lies das Gerät und die Ausleihe. Entwirf die Erinnerung, versende nichts. ...", "en": "Read the device and the loan. Draft the reminder, send nothing. ..."}, "buttons": [ {"entity": "geraet", "label": {"de": "An Rückgabe erinnern", "en": "Remind about return"}, "prompt": {"de": "Entwirf eine Erinnerung zur Rückgabe von {{title}} (fällig am {{record.rueckgabe}}).", "en": "Draft a reminder to return {{title}} (due {{record.rueckgabe}})."}} ]} ]}| Feld | Bedeutung |
|---|---|
aiTools.entities | Je genanntem Fachobjekt bekommt der KI-Assistent die Werkzeuge zum Suchen und Lesen für Personen mit Leserecht und die Aktionen zum Anlegen und Ändern für Personen mit Schreibrecht. |
aiTools.actions | Der KI-Assistent darf jede genannte Aktion vorschlagen. "aiTool": true an der Aktion bewirkt dasselbe. Nie für eine Mail-Aktion. |
assistant.instructions | Wird als Skill des Mandanten angelegt, immer aktiv, abgeschaltet, solange das Modul deaktiviert ist, und entfernt, wenn es deinstalliert wird. |
assistant.prompts | Beispiel-Anweisungen, die als Chips in der Assistentenspalte erscheinen, wenn die genannte Ansicht offen ist. label und prompt sind Pflicht, mindestens mit deutschem Prompt. |
assistant.avatars | Bis zu 5 Avatare, angelegt als firmenweite Avatare des Mandanten. key (kebab-case) und name sind Pflicht; role, gradient, persona (höchstens 1200 Zeichen), greeting und expertise (Schlagworte ohne Komma) sind optional. |
assistant.skills | Bis zu 20 Skills, angelegt als firmenweite Skills, die der freie Chat über ihre description findet. key, name, description und instructions sind Pflicht; avatar nennt einen Avatar des Pakets, der dann antwortet. |
assistant.skills[].buttons | Bis zu 3 KI-Knöpfe je Skill auf den Datensatzseiten von entity: einem Fachobjekt dieses Pakets oder einer Kernseite als core:partner, core:dmsDocument, core:invoice oder core:project. Ein Klick öffnet einen Dialog mit dem Skill, seinem Avatar und dem prompt, gefüllt aus dem Datensatz; die Person kann ihn vor dem Senden ändern. Platzhalter: {{title}} und {{record.<feld>}}, wobei das Feld am Fachobjekt des Pakets existiert (auf einer Kernseite geht jeder Feldname durch). label höchstens 80 Zeichen, prompt höchstens 2000. |
Die Texte werden in der Standardsprache des Mandanten angelegt (AdministrationBetriebSystemkonfiguration), sonst auf Englisch, sonst auf Deutsch. Einen Knopf sehen Personen, die das Fachobjekt lesen (auf einer Kernseite: die diese Seite öffnen) und den Skill sehen dürfen, solange das Modul aktiv ist. Ein Update schreibt Avatare, Skills und Knöpfe anhand ihres Schlüssels neu; was eine neue Version nicht mehr hat, wird entfernt, ihre Avatare werden archiviert, damit ihre Gespräche bleiben. Die Knöpfe eines Paket-Skills lassen sich im Skill-Editor nicht ändern.
Jedes Werkzeug und jede Aktion läuft mit den Rechten der Person, die chattet. Selbst schreibt der KI-Assistent nie: Er schlägt die Aktionen als Karte im Chat vor, der Server prüft bei jedem Schritt Eingaben, Zustand und Berechtigung, und erst der Klick der Person führt sie aus, mehrere auf einmal in einer Transaktion. Eine Freigabe, die nur ein Mensch erteilen soll, etwa für ein Schreiben, ist deshalb eine Aktion ohne aiTool. Mehr dazu unter Mit dem KI-Assistenten arbeiten.
Startseiten-Vorlagen: dashboards
Abschnitt betitelt „Startseiten-Vorlagen: dashboards“Eine Vorlage ersetzt den ganzen Aufbau der Startseite mit einem Klick (siehe Mit einer Vorlage beginnen). Ein Paket kann bis zu drei eigene Vorlagen mitbringen, meist eine je Rolle, die es anlegt. Sie bestehen aus den Kacheln des Pakets (Ansichten mit slot dashboard.widget) und eingebauten Kacheln.
"dashboards": [ {"key": "ausgabe", "label": {"de": "Geräteausgabe", "en": "Equipment desk"}, "description": {"de": "Was heute zurückkommt und was verliehen ist."}, "icon": "inventory_2", "roles": ["verwaltung"], "tiles": [ {"view": "heute-faellig"}, {"view": "verliehen", "width": 2}, {"widget": "calendar", "width": 2}, {"widget": "ask"} ]}]| Feld | Bedeutung |
|---|---|
key | Eindeutig in der Liste. |
label | Der Name der Vorlage, höchstens 60 Zeichen je Sprache. |
description | Optional, höchstens 300 Zeichen. |
icon | Optional, ein Name aus Material Symbols. Ohne Angabe das Symbol des Pakets. |
roles | Optional, Schlüssel aus den roles des Pakets, deren Inhaber auf dieser Vorlage starten. Eine Rolle nennt höchstens eine Vorlage. |
tiles | 1 bis 12 Kacheln in der Reihenfolge auf der Seite. Jede nennt genau eins von view und widget, keine Kachel zweimal. Die Begrüßung steht immer oben und wird nicht aufgeführt. |
tiles[].view | Eine Ansicht dieses Pakets mit slot dashboard.widget. |
tiles[].widget | Eine eingebaute Kachel ohne Einstellungen: ask (Frag den Assistenten), onboarding (Einrichtung), ai-sessions (AI Gateway), users (Benutzer) oder calendar (Kalender). Auswertungen und eingebettete Ansichten gehen nicht, weil ihre Einstellungen auf die Daten des Mandanten zeigen. |
tiles[].width | Optional, die Breite in Spalten. Eine Ansicht der Größe s 1 bis 2, der Größe m oder l 2 bis 4; ask und onboarding 2 bis 4, ai-sessions und users 1 bis 2, calendar 1 bis 4. Ohne Angabe beginnt eine Ansicht mit 1 (s), 2 (m) oder 4 (l) Spalten, eine eingebaute Kachel mit ihrer üblichen Breite. |
Die Vorlagen stehen vor Standard, KI und Leer, mit dem Namen des Moduls neben der Zahl der Kacheln, etwa 4 Widgets · Modul Leihgeräte. Eine Person sieht sie, solange das Modul aktiv ist und sie es nutzen darf, wie beim Menü des Moduls, in der Standardsprache des Mandanten. Beim Anwenden fehlt eine eingebaute Kachel, die die Person nicht sehen darf, etwa der Kalender ohne das Recht dazu. nexxus liest die Vorlagen bei jedem Aufruf aus dem Manifest: Ein Update ändert sie sofort, Deaktivieren oder Entfernen nimmt sie weg. Ein schon angewendeter Aufbau behält seine Kacheln. Eine Vorlage ist keine Vorgabe für alle; dafür wendet ein Admin sie an und speichert den Aufbau als Vorgabe.
Mit roles ist eine Vorlage auch die Startseite dieser Rollen. Wer eine davon hat und sein Dashboard nicht angepasst hat, sieht die Vorlage gleich nach der Anmeldung, vor der Vorgabe des Mandanten und Standard, und auf der Karte steht Startseite für Geräteverwaltung. Sie bleibt lebendig: Ein Update ändert sie auch für diese Personen, bis sie ihr Dashboard bearbeiten oder eine Vorlage anwenden. Hat jemand Rollen aus zwei Paketen, gewinnt das Paket, dessen Schlüssel alphabetisch zuerst kommt. In einer solchen Startseite erscheint eine Paket-Kachel nur, wenn ihr Modul aktiv ist und die Person es nutzen darf, auch bei Administratoren; nexxus prüft das Modul, nicht was die Ansicht liest. Legen Sie in die Vorlage einer Rolle also nur Kacheln, die diese Rolle ganz lesen darf.
Ein Paket mit dashboards braucht nexxus 0.14.0 oder neuer und gibt deshalb "platform": ">=0.14.0 <1.0.0" an. Eine ältere Version lehnt es ohnehin ab, weil sie das Feld nicht kennt.
Migrationen
Abschnitt betitelt „Migrationen“"migrations": { "1.1.0": { "pre": [{"op": "renameValue", "entity": "geraet", "field": "kategorie", "from": "telefon", "to": "smartphone"}], "post": [{"op": "setDefault", "entity": "geraet", "field": "standort", "value": "buero"}] }}Jeder Schlüssel ist eine Version bis einschließlich der eigenen Paketversion. Die Schritte setDefault, renameValue, renameField, copyField, deleteField und deleteEntity (die letzten beiden nur in einer neuen Hauptversion) und ihre Reihenfolge beschreibt Daten und Aktualisierungen.
Pakete sind in sich geschlossen
Abschnitt betitelt „Pakete sind in sich geschlossen“Ein Paket arbeitet mit seinen eigenen Fachobjekten, mit Kerndaten (den core:-Zielen oben) und mit nichts sonst. Es baut nie auf einem anderen Paket auf: Kein Verweis, keine Ansicht, kein Scope, kein Ereignis und keine Datenzeile nennt Fachobjekte eines anderen Pakets, und requires nennt nur eingebaute Module. Was zusammengehört, kommt als ein Paket. Bis zum 9. Oktober 2026 konnten Pakete aufeinander aufbauen. Das band Installationen aneinander (eine musste zuerst installiert sein und ließ sich nicht deaktivieren, solange die andere sie nutzte) und verteilte einen Fachbereich auf zwei Installationen.
nexxus lehnt ein Paket beim Hochladen ab, wenn es ein anderes Paket nennt. Jeder Befund ist eine Zeile, die mit der Stelle und „Pakete bauen nicht aufeinander auf:“ beginnt:
| Wo | Abgelehnt | Meldung nach der Stelle |
|---|---|---|
requires | ein Paketschlüssel | „requires nennt das Paket ‘muster.akten’. Was zusammengehört, gehört in ein Paket; requires nennt nur Kernmodule“ |
scopes | pkg:<paket>:<fachobjekt>:read oder :write | „scope ‘pkg:muster.akten:akte:read’ greift auf Datensätze des Pakets ‘muster.akten’ zu“ |
target eines Verweises oder einer Ansicht | <paket>:<fachobjekt> | „target ‘muster.akten:akte’ ist ein Datenobjekt des Pakets ‘muster.akten’“ |
services[].events | <paket>:<fachobjekt>.<op> | „Ereignis ‘muster.akten:aktendokument.created’ kommt aus dem Paket ‘muster.akten’“ |
data/, demo/ | {"$key": "<paket>:<fachobjekt>:<key>"} | „$key ‘muster.akten:akte:2026-0201’ nennt einen Datensatz des Pakets ‘muster.akten’“ |
Schreiben Sie ein Fachobjekt nur mit seinem Schlüssel. Der eigene Paketschlüssel davor (acme.besucher:besuch in acme.besucher) nennt kein anderes Paket, ist aber auch nicht die Schreibweise des Manifests: Ein so geschriebenes Ziel lehnt nexxus mit „target ‘acme.besucher:besuch’ ist kein Datenobjekt des Pakets“ ab.
Eine Version, die vor dieser Regel hochgeladen wurde, kann noch ein anderes Paket nennen. nexxus installiert sie nicht und aktualisiert nicht auf sie: „Diese Version baut auf dem Paket ‘muster.akten’ auf. Pakete bauen nicht mehr aufeinander auf; was zusammengehört, kommt als ein Paket.“ Eine Installation, die schon auf einer solchen Version läuft, läuft weiter, erreicht vom anderen Paket aber nichts.
Von den Fachbereichen des Mandanten hängt ein Paket ebenfalls nicht ab, siehe Fachbereiche. Die Fallstudie CuriaOS zeigt ein Paket, das aus zweien entstanden ist.
Wie nexxus ein Paket prüft
Abschnitt betitelt „Wie nexxus ein Paket prüft“Archiv
ZIP lesbar, höchstens 2 000 Dateien und 64 MB entpackt, sichere Pfade.
Schema
module.json gegen das Paketschema: Typen, Pflichtfelder, keine unbekannten Felder.
Regeln
Verweise zwischen den Teilen, Schlüssel, Bereiche, Dateien, Überschrift im Changelog.
Hashes und Signatur
MANIFEST.sha256 stimmt, die Signatur ist gültig, oder das Paket gilt als unsigniert.
Die Regeln gehen über das Schema hinaus. Einige davon mit der Meldung, die nexxus ausgibt:
| Regel | Meldung |
|---|---|
publisher passt zum Schlüssel | „publisher ‘acme’ muss dem Teil vor dem Punkt in ‘firma.besucher’ entsprechen“ |
area ist ein bekannter Bereich | „area ‘lager’ ist kein Menübereich (…)“ |
| Filter und Sortierung nur auf indizierten Feldern | „Filter ‘name’ ist nicht indexed“ |
| Menüeinträge zeigen auf Listen oder Seiten | „nur Listen und Seiten (slot page) gehören ins Menü“ |
| Rollen nennen vorhandene Rechte | „Rolle ‘empfang’ nennt das unbekannte Recht ‘besuch.delete’“ |
| Reservierte Namen für Fachobjekte | „Datenobjekt ‘p’ ist ein reservierter Name“ |
| Pakete bauen nicht aufeinander auf | „Pakete bauen nicht aufeinander auf: scope ‘pkg:muster.akten:akte:read’ greift auf Datensätze des Pakets ‘muster.akten’ zu“ |
| Überschrift im Changelog für die Version | „CHANGELOG.md: keine Überschrift ’## [1.0.0]’ für die Version aus module.json“ |
| Ansichtsdateien sind da und klein genug | „ui/empfang.html fehlt“, „ui/empfang.html ist größer als 2 MB“ |
| Eine Mail-Aktion braucht ihren Scope | „scope ‘core:mail:send’ muss unter scopes stehen“ |
| Eine Mail-Aktion ist nie ein KI-Werkzeug | „eine Mail-Aktion ist nie ein KI-Werkzeug (aiTool): ein Mensch löst jeden Versand aus“ |
| Die Platzhalter einer Mail gibt es | „Platzhalter ‘{{payload.betreff}}’ gibt es in Mails nicht: was hinausgeht, steht vorher unverändert in der Vorschau“ |

Jede Meldung beginnt mit der Stelle, auf die sie sich bezieht, etwa module.json views.besuch.list:. Beim Hochladen zeigt der Katalog „<Datei>“ wurde nicht angenommen., darunter den Satz „Das Paket ist fehlerhaft:“ und alle Probleme als Liste. Vor dem Hochladen prüft der Befehl verify des Paketierungsskripts Hashes, Dateiliste und Changelog-Überschrift; die Regeln des Manifests prüft nur nexxus selbst.