Automatisierungen mit Lua schreiben
Die versionierte Werkstube-Lua-Standardbibliothek mit Kontext, Text, Listen, Datum, Geld und sicheren Aktionen verwenden.

Mit einer festen API-Version beginnen
Neue Skripte beginnen in der ersten nichtleeren Zeile mit dieser Direktive:
-- @werkstube api=1
Damit bleibt die Bedeutung deines Skripts stabil, auch wenn später eine neue API-Version hinzukommt. Eine unbekannte, fehlerhafte oder weiter unten platzierte Direktive wird beim Speichern abgelehnt.
API 1 teilt die verfügbaren Funktionen bewusst in drei globale Tabellen:
ctxenthält unveränderliche Daten des aktuellen Laufs.stdverarbeitet Werte ohne Nebenwirkungen.actionprotokolliert, ruft KI auf oder führt berechtigungsgeprüfte Werkstube-Aktionen aus.
-- @werkstube api=1
local job_id = ctx.require("entity_id", "Auftrags-ID fehlt")
local title = std.text.normalize_space(ctx.get("payload.title", "Auftrag"))
action.jobs.add_history{
jobId = job_id,
text = title .. " wurde automatisch geprüft.",
actor = "Automatisierung",
}
Laufkontext mit ctx
ctx.trigger enthält das vollständige Ereignis. Bei optionalen oder verschachtelten Feldern sind Punktpfade sicherer:
local status = ctx.get("payload.status", "unbekannt")
local first_item = ctx.get("payload.items.1")
local invoice_id = ctx.require("entity_id", "Rechnungs-ID fehlt")
ctx.require(pfad, meldung) beendet den Lauf mit deiner Meldung, wenn der Wert fehlt. Prüfe Pflichtwerte, bevor eine Aktion ausgeführt wird.
Weitere feste Kontextwerte sind:
ctx.dry_run—truewährend eines Probelaufsctx.started_at— stabiler Laufstart als RFC-3339-Zeitpunktctx.timezone— derzeitEurope/Berlinctx.limits— die tatsächlich aktiven Laufzeit- und Datengrenzen
Werte mit std verarbeiten
Die Standardbibliothek ist unter std verfügbar. Ihre Funktionen verändern keine Werkstube-Daten und liefern für dieselben Eingaben dasselbe Ergebnis.
| Bereich | Funktionen |
|---|---|
| Text | trim, is_blank, normalize_space, contains, starts_with, ends_with, split, join, replace, truncate |
| Listen | map, filter, find, any, all, unique, sort_by, sum, group_by |
| Objekte | get, has, keys, values, copy, merge |
| Datum | today, parse, format, add_days, diff_days, is_weekend |
| Geld | parse, percent, add_tax, allocate, format |
| JSON | encode, decode |
| Validierung | required, one_of, email |
Die rechte Referenz im Skripteditor ist der vollständige, durchsuchbare Funktionskatalog. Ein Klick fügt den exakten Funktionsnamen oder das passende Snippet ein.
Text, Listen und Objekte
local names = std.list.map(ctx.get("payload.items", {}), function(item)
return std.text.normalize_space(item.name)
end)
local total = std.list.sum(ctx.get("payload.items", {}), "amount_cents")
local grouped = std.list.group_by(ctx.get("payload.items", {}), "status")
local email = std.object.get(ctx.trigger, "payload.customer.email")
action.log(std.text.join(names, ", "), total, grouped, email)
Listen müssen fortlaufende Indizes ab 1 haben. sort_by liefert eine stabil sortierte Kopie, unique behält das erste Vorkommen und object.keys sortiert Schlüssel lexikografisch. copy und merge erzeugen tiefe Kopien; Eingabetabellen werden nicht verändert.
Datum und Zeitzone
local today = std.date.today()
local due = std.date.add_days(today, 14)
local label = std.date.format(due, "%d.%m.%Y")
if std.date.is_weekend(due) then
action.warn("Das Zieldatum " .. label .. " liegt am Wochenende")
end
Datumsfunktionen arbeiten mit Kalenderdaten im Format YYYY-MM-DD. parse nimmt außerdem RFC-3339-Zeitpunkte an und rechnet sie in ctx.timezone um. Unterstützte Formatteile sind %Y, %m und %d. Ein Tag ist ein Kalendertag, nicht pauschal 24 Stunden.
Geld ohne Fließkommafehler
Geldwerte sind immer ganzzahlige Cent. Prozentwerte werden als Basispunkte angegeben: 1900 bedeutet 19 Prozent.
local net_cents = std.money.parse("1250,00")
local gross_cents = std.money.add_tax(net_cents, 1900)
local shares = std.money.allocate(gross_cents, { 2, 1 })
action.log(std.money.format(gross_cents), shares)
std.money.parse akzeptiert Dezimalbeträge wie 1250, 1250,5 oder 1250.50, aber keine Tausendertrennzeichen. percent und add_tax runden halbe Cent von null weg. allocate verteilt den Restcent deterministisch und erhält die Gesamtsumme.
Striktes JSON und Validierung
local data = std.json.decode('{"priority":"high"}')
local priority = std.validate.one_of(data.priority, { "normal", "high" })
local recipient = std.validate.email(ctx.require("payload.email"))
action.log(std.json.encode({ priority = priority, recipient = recipient }))
std.json akzeptiert nur endliche JSON-Werte, begrenzte Listen oder Objekte und keine gemischten Tabellenschlüssel, Zyklen oder Funktionen. Die Kodierung ist deterministisch. std.validate.required lehnt auch leere Texte ab; one_of vergleicht Typ und Wert.
Werkstube-Aktionen
Aktionen behalten ihre fachlichen Bereiche:
action.customers— Kunden anlegen, laden und suchenaction.jobs— Aufträge anlegen, laden, weiterschalten und Verlauf ergänzenaction.inquiries,action.quotes,action.invoices— kaufmännische Vorgängeaction.calendar— Termine anlegenaction.inventory— Bestand, Nachbestellung und Bestellungen
Die genaue Liste hängt von den Betriebsmodulen und Rechten der ausführenden Person ab. Die Editor-Referenz kennzeichnet nicht verfügbare Aktionen und verhindert, dass ein Beispiel mit fehlenden Rechten unbemerkt eingesetzt wird. Der Server prüft dieselben Rechte bei jedem Lauf erneut.
action.notify("Titel", "Text", {
severity = "warning", -- info | success | warning | danger
link = "/auftraege/42",
})
action.email("chef@example.de", "Betreff", "Nachricht")
local summary = action.ai("Fasse die Eingabe sachlich zusammen: " .. ctx.get("payload.text", ""))
Fehler und Protokoll
local ok, result = pcall(function()
return action.jobs.get{ id = ctx.require("entity_id") }
end)
if not ok then
action.warn("Auftrag konnte nicht geladen werden: " .. tostring(result))
return
end
action.log("Auftrag geladen", result)
action.log, action.warn und action.error schreiben strukturierte Tabellen lesbar ins Laufprotokoll. action.error protokolliert nur; zum Abbrechen verwendest du error("Begründung") oder ctx.require. Behandle Aktionsfehler nur, wenn der Ablauf danach fachlich sicher enden darf.
Sicherheitsgrenzen
Lua läuft ohne Zugriff auf Betriebssystem, Dateien, Prozesse, Netzwerk-Sockets oder native Bibliotheken. Gesperrt sind insbesondere os, io, debug, package, syscall, ffi, socket, http, require und dynamisches Laden von Code.
API 1 begrenzt jeden Lauf auf:
- 64 KB Skriptquelle
- 10 Sekunden Laufzeit
- 64 KB Laufprotokoll
- 2.000 Tabelleneinträge und 10 Verschachtelungsebenen bei Standardbibliotheks- und JSON-Operationen
Lies die aktuellen Werte programmatisch unter ctx.limits, statt sie im Skript zu wiederholen.
Bestehende Skripte
Skripte ohne Direktive laufen weiterhin mit API 1 und den historischen Aliasen werk.*, action.trigger, action.get, action.require und action.json. Sie sind für Kompatibilität vorhanden, aber als veraltet markiert. Bei Änderungen empfiehlt sich die schrittweise Umstellung auf ctx, std und die fachlichen action-Bereiche.
Vollständige, direkt ausführbare Vorlagen findest du unter Beispiele für Automatisierungen.
Vor dem Aktivieren
- Pflichtwerte mit
ctx.requireprüfen. - Syntaxanzeige im Editor abwarten.
- Einen Probelauf mit dem Beispiel-Ereignis ausführen.
- Im Protokoll Eingaben, Ergebnisse und gewählten Pfad prüfen.
- Irreversible Schritte wie E-Mail oder Bestellung möglichst ans Ende setzen.
Mehr zum sicheren Wiederholen fehlgeschlagener Läufe findest du unter Testen & Laufprotokolle.