Cookbook (Praxisbeispiele)
Zuletzt aktualisiert: 17. Juli 2026
Verschiedene Szenarien werden als vollständige ScriptDefinition gezeigt. Die Syntaxgrundlage finden Sie im Statement-Katalog und unter Wertausdrücke; zu Ausführung und Einschränkungen siehe Ausführungssemantik, Einschränkungen und Sicherheit. In allen Beispielen ist der fields-Wert beim Schreiben eine Locale-Map ({ "<locale>": Wert }), und die Beispiel-Locale ist einheitlich en-US. Beispiele mit einem externen Aufruf (Http) oder der Ingestion einer Media-Datei haben executionMode auf "Async" gesetzt.
Inhaltsverzeichnis
- Grundlegendes CRUD: 1. Content erstellen und veröffentlichen · 2. Update mit berechnetem Wert · 3. Read-only GET · 4. Einzeln lesen, guard, dann genehmigen
- Nachschlagen und upsert: 5. slug upsert · 6. Dynamischer Feld-Schlüssel und Locale-patch
- Externe API: 7. Credit vorab abziehen (CAS), LLM-Aufruf, Rückerstattung · 8. Bild-URL zu Media · 9. base64-Bild zu Media · 10. Bedingte Verarbeitung nach Moderation · 11. try/catch fallback
- Parallel: 12. Nach parallelen Aufrufen zusammenführen · 13. Registrierungsprüfung
- Schleifen und Aggregation: 14. N aus einem Array-Input erstellen · 15. Counted-loop-Seed · 16. Cascade-Löschung · 17. Schleifenakkumulation: Summe · 18. Durch alle Seiten paginieren · 19. id-Batch-Sammlung
- Saga und Nebenläufigkeit: 20. Zahlungs-Saga · 21. Optimistische Sperre (CAS)
Grundlegendes CRUD
1. Content erstellen und veröffentlichen
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" }, "body": { "en-US": "{ /payload/fields/body }" } },
"publish": true, "name": "post" },
{ "type": "Return", "value": { "id": "{ /post/sys/id }" }, "statusCode": 201 } ] }2. Update mit berechnetem Wert (Aufrufzähler +1)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } } ] }3. Read-only GET: meine Bestellliste
{ "method": "Get", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "createdBy": ":self" }, "order": "-sys.createdAt", "limit": 20, "name": "orders" },
{ "type": "Return", "value": { "orders": "{ /orders/items }", "next": "{ /orders/next }" } } ] }Script wird nicht nur zum Schreiben verwendet, sondern auch als Lese-BFF-Endpunkt. Mit createdBy: ":self" wird „nur das Eigene" abgefragt und das Ergebnis unverändert zurückgegeben.
4. Einzeln lesen, guard, dann genehmigen
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "!=": [ "{ /order/fields/status/en-US }", "pending" ] },
"then": [ { "type": "Return", "value": { "reason": "not pending" }, "isError": true, "statusCode": 409 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "approved" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ] }Wenn Sie mit ResourceRead einen einzelnen Datensatz an einen Namen binden, können Sie ihn direkt über { /order/fields/... } referenzieren (kein items/0 nötig). Existiert er nicht, führt der Lesevorgang zu einem Fehler (Sie können ihn mit Try umschließen).
Nachschlagen und upsert
5. slug upsert (find-then-upsert)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" },
{ "type": "If", "condition": { "!!": "{ /found/sys/id }" },
"then": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /found/sys/id }" } },
"fields": { "body": { "en-US": "{ /payload/fields/body }" } } },
{ "type": "Return", "value": { "id": "{ /found/sys/id }", "op": "updated" } } ],
"else": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"fields": { "slug": { "en-US": "{ /payload/fields/slug }" }, "body": { "en-US": "{ /payload/fields/body }" } }, "name": "created" },
{ "type": "Return", "value": { "id": "{ /created/sys/id }", "op": "created" }, "statusCode": 201 } ] } ] }ResourceFind bindet die erste Übereinstimmung direkt (oder null, falls keine vorhanden ist), und { "!!": "{ /found/sys/id }" } verzweigt danach, ob sie existiert.
6. Dynamischer Feld-Schlüssel und dynamischer Locale-patch
{ "method": "Patch", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "{ /payload/fields/fieldKey }": { "{ /payload/fields/locale }": "{ /payload/fields/value }" } } } ] }Sowohl der Feld-Schlüssel als auch der Locale-Bucket-Schlüssel sind { /ptr }-Referenzen. Verwenden Sie dies, wenn Sie eine Übersetzung in einen bestimmten Locale-Bucket einfügen möchten.
Externe API
7. Credit-guard, Vorab-Abzug (CAS), LLM-Aufruf, Rückerstattung bei Fehler (repräsentatives Beispiel)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_wallet" } },
"where": { "createdBy": ":self" }, "name": "wallet" },
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "isError": true, "statusCode": 402 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"version": "{ /wallet/sys/version }",
"fields": { "balance": { "en-US": { "-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } },
"name": "charged" } ],
"catch": [ { "type": "Return", "value": { "ok": false, "reason": "version conflict, erneut versuchen" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/choices/0/message/content }" } }, "name": "out" },
{ "type": "Return", "value": { "ok": true, "id": "{ /out/sys/id }", "remaining": "{ /charged/fields/balance/en-US }" } } ],
"catch": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"fields": { "balance": { "en-US": { "+": [ "{ /charged/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } } },
{ "type": "Return", "value": { "ok": false, "reason": "generation failed, refunded" }, "isError": true, "statusCode": 502 } ] } ] }Mit einem guard wird zunächst geprüft, ob das Guthaben ausreicht, und dann vor dem externen Aufruf abgezogen. Der Abzug setzt eine optimistische Sperre (CAS) auf die sys.version des wallet. Hat eine andere Ausführung das wallet zwischen dem Lesen des Guthabens und dem Abzug verändert, wird wegen einer Versionsabweichung abgebrochen und catch gibt 409 zurück. Da noch kein externer Aufruf erfolgt ist, werden gleichzeitige Anfragen nicht doppelt belastet. Erst nachdem der Abzug festgeschrieben ist, wird das LLM aufgerufen; schlägt dieser Aufruf fehl, addiert catch den abgezogenen Betrag (cost) wieder hinzu, um eine Rückerstattung (Kompensation) vorzunehmen, und gibt dann 502 zurück. Die Reihenfolge lautet: die Abbuchung vor dem unumkehrbaren externen Aufruf festschreiben und nur im Fehlerfall kompensieren. Der geheime Schlüssel wird in einem secret:true-Header abgelegt. Zu den Grenzen der Kompensation siehe Keine Transaktionen und Kompensation in der Ausführungssemantik.
8. Bild (URL) als Media erstellen und an Content anhängen
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } } }, "name": "img" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_artwork" } },
"fields": { "prompt": { "en-US": "{ /payload/fields/prompt }" }, "image": { "en-US": "{ /img/sys/id }" } } } ] }Das Media wird mit einem name erstellt, und ResourceCreate (Content) steckt { /img/sys/id } in das Referenzfeld. Media verwendet dasselbe fields-Modell wie Content. file ist die Ingestion-Anweisung { source, encoding }. Da es sich um eine Datei-Ingestion handelt, ist es Async.
9. base64-Bild zu Media
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "file": { "en-US": { "source": "{ /gen/body/data/0/b64_json }", "encoding": "base64" } } } } ] }10. Bedingtes publish oder Löschen nach Moderation
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.mod.com/check",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "text": "{ /payload/fields/body }" }, "name": "mod" },
{ "type": "If", "condition": { "==": [ "{ /mod/body/flagged }", true ] },
"then": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ],
"else": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] } ] }11. try/catch: fallback bei externem Fehler
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://primary.api/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 8000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" }, "source": { "en-US": "primary" } } } ],
"catch": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "Generierung fehlgeschlagen" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ] }Parallel
12. Zwei parallele externe Aufrufe zu Content zusammenführen
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ] ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_merged" } },
"fields": { "left": { "en-US": "{ /a/body/value }" }, "right": { "en-US": "{ /b/body/value }" } } } ] }13. Registrierungsprüfung: parallele Scores, dann and-Entscheidung
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "POST", "url": "https://api.fraud.com/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "email": "{ /payload/fields/email }" }, "name": "fraud" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.credit.com/v1/{ /payload/fields/userId }/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ], "name": "credit" } ] ] },
{ "type": "If",
"condition": { "and": [ { "<": [ "{ /fraud/body/risk }", 0.5 ] }, { ">=": [ "{ /credit/body/score }", 700 ] } ] },
"then": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"fields": { "email": { "en-US": "{ /payload/fields/email }" }, "status": { "en-US": "approved" } } },
{ "type": "Return", "value": { "decision": "approved" }, "statusCode": 201 } ],
"else": [ { "type": "Return", "value": { "decision": "manual-review" }, "statusCode": 202 } ] } ] }Sie können { /ptr } auch in den URL-Pfad einfügen. Branch-Ergebnisse werden nach dem Join referenziert. Es gibt zwei externe Aufrufe (höchstens drei).
Schleifen und Aggregation
14. N Content aus einem Array-Input (Loop over)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "item", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" }, "qty": { "en-US": "{ /item/qty }" } } } ] } ] }15. Counted loop (for): Slots seeden
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "for": { "from": 1, "to": 5 }, "as": "i", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_slot" } },
"fields": { "index": { "en-US": "{ /i }" }, "status": { "en-US": "open" } } } ] } ] }for läuft von from bis to, inklusive (Ganzzahl-Literale, step ist standardmäßig 1). as bindet den aktuellen Zähler an { /i }.
16. Cascade-Löschung (PageRead, Loop, Delete)
{ "method": "Delete", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "limit": 100, "name": "comments" },
{ "type": "Loop", "over": "{ /comments/items }", "as": "c", "maxIterations": 100,
"body": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /c/sys/id }" } } } ] },
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] }Bei mehr als 100 verwenden Sie 18. Durch alle Seiten paginieren.
17. Schleifenakkumulation: SetVar-Summe
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "total", "value": 0 },
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "row", "maxIterations": 100,
"body": [ { "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_summary" } },
"fields": { "totalQty": { "en-US": "{ /vars/total }" } } } ] }18. Durch alle Seiten paginieren
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "cursor", "value": null },
{ "type": "SetVar", "var": "hasMore", "value": true },
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000,
"body": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "limit": 100, "cursor": "{ /vars/cursor }", "name": "page" },
{ "type": "Loop", "over": "{ /page/items }", "as": "p", "maxIterations": 100,
"body": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /p/sys/id }" } } } ] },
{ "type": "SetVar", "var": "cursor", "value": "{ /page/next }" },
{ "type": "SetVar", "var": "hasMore", "value": { "!!": "{ /page/next }" } } ] } ] }cursor, while und SetVar durchlaufen alle Seiten. Da externe Aufrufe innerhalb eines Loop-body verboten sind, werden hier nur Ressourcenoperationen verwendet.
19. id-Batch-Sammlung aus einer E-Mail-Liste (merge)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "ids", "value": [] },
{ "type": "SetVar", "var": "missing", "value": [] },
{ "type": "Loop", "over": "{ /payload/fields/emails }", "as": "email", "maxIterations": 100,
"body": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"where": { "fields.email": { "eq": "{ /email }" } }, "name": "acc" },
{ "type": "If", "condition": { "!!": "{ /acc/sys/id }" },
"then": [ { "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /acc/sys/id }" ] ] } } ],
"else": [ { "type": "SetVar", "var": "missing", "value": { "merge": [ "{ /vars/missing }", [ "{ /email }" ] ] } } ] } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_campaign" } },
"fields": { "recipients": { "en-US": "{ /vars/ids }" }, "unresolved": { "en-US": "{ /vars/missing }" } } } ] }Ein Lesevorgang (ResourceFind) ist kein externer Aufruf und daher innerhalb eines Loop-body erlaubt. Vorhandensein und Fehlen werden jeweils mit merge akkumuliert.
Saga und Nebenläufigkeit
20. Zahlungs-Saga (Try/catch/finally)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"fields": { "sku": { "en-US": "{ /payload/fields/sku }" }, "status": { "en-US": "reserved" } },
"publish": false, "name": "order" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.pay.com/charge",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "amount": "{ /payload/fields/amount }", "ref": "{ /order/sys/id }" }, "timeoutMs": 10000, "name": "pay" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "txId": { "en-US": "{ /pay/body/transactionId }" } }, "publish": true },
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 } ],
"catch": [
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } } },
{ "type": "Return", "value": { "reason": "payment failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 402 } ],
"finally": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_paylog" } },
"fields": { "orderRef": { "en-US": "{ /order/sys/id }" }, "amount": { "en-US": "{ /payload/fields/amount }" } } } ] } ] }Nach der Reservierung (draft): bei erfolgreicher Zahlung Bestätigung, publish und 201; bei einem Fehler löscht catch die Reservierung (Kompensation) und gibt 402 zurück; finally protokolliert immer. Da die löschbasierte Kompensation eine neue sys.id erzeugt, fällt sie unter die Einschränkung, bei der Referenzen brechen (siehe Keine Transaktionen und Kompensation in der Ausführungssemantik).
21. Optimistische Sperre (CAS)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_stock" } },
"where": { "fields.sku": { "eq": "{ /payload/fields/sku }" } }, "limit": 1, "name": "stock" },
{ "type": "If", "condition": { "<": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] },
"then": [ { "type": "Return", "value": { "reason": "out of stock" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /stock/items/0/sys/id }" } },
"version": "{ /stock/items/0/sys/version }",
"fields": { "qty": { "en-US": { "-": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] } } } },
{ "type": "Return", "value": { "ok": true } } ],
"catch": [
{ "type": "Return", "value": { "reason": "version conflict, erneut versuchen" }, "isError": true, "statusCode": 409 } ] } ] }Lesen Sie den Bestand, um eine frische sys.version zu erhalten, und ziehen Sie dann mit dieser Version ab (version). Hat eine andere Ausführung den Wert zwischen dem Lesen und dem Schreiben verändert, wird wegen einer Versionsabweichung abgebrochen und catch gibt 409 zurück. Der guard für unzureichenden Bestand liegt außerhalb von Try (normaler vorzeitiger Ausstieg).
Verwandte Dokumente
- Statement-Katalog: Die Felder und Ergebnisse der in den Beispielen verwendeten Statements.
- Wertausdrücke: Referenzen, JsonLogic und Locale-Map-Regeln.
- Ausführungssemantik, Einschränkungen und Sicherheit: Ausführungsreihenfolge, Kompensation, optimistische Sperre und Einschränkungen.
- Script-Übersicht: Die oberste Struktur und die Ausführungsmodi.
