Cookbook (Kumpulan Contoh Praktis)

Terakhir diperbarui: 18 Juli 2026

Berbagai skenario ditampilkan sebagai ScriptDefinition yang lengkap. Untuk dasar sintaksisnya, lihat Katalog Statement dan Ekspresi Nilai; untuk eksekusi dan batasan, lihat Semantik Eksekusi, Batasan, dan Keamanan. Pada setiap contoh, nilai fields untuk penulisan berupa peta locale ({ "<locale>": nilai }), dan locale contoh diseragamkan menjadi en-US. Contoh yang memiliki panggilan eksternal (Http) atau ingest file Media memiliki executionMode bernilai "Async".

Daftar Isi

CRUD Dasar

1. Membuat dan mempublikasikan Content

{ "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 dengan nilai terkomputasi (jumlah tampilan +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. GET hanya-baca: daftar pesanan saya

{ "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 dipakai tidak hanya untuk menulis, tetapi juga sebagai endpoint BFF baca. Dengan createdBy: ":self", ia mengambil "hanya milik saya" dan mengembalikan hasilnya apa adanya.

4. Baca satu, guard, lalu setujui

{ "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 } } ] }

Dengan mengikat satu item ke sebuah nama menggunakan ResourceRead, Anda dapat merujuknya langsung sebagai { /order/fields/... } (tidak perlu items/0). Jika tidak ada, pembacaan menghasilkan kesalahan (Anda bisa membungkusnya dengan Try).

Pencarian dan 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 mengikat langsung kecocokan pertama (atau null jika tidak ada), dan { "!!": "{ /found/sys/id }" } bercabang berdasarkan ada tidaknya.

6. Kunci field dinamis dan patch locale dinamis

{ "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 }" } } } ] }

Baik kunci field maupun kunci bucket locale sama-sama merupakan referensi { /ptr }. Gunakan ini saat Anda ingin menaruh terjemahan ke dalam bucket locale tertentu.

API Eksternal

7. Guard kredit, potong di muka (CAS), panggilan LLM, refund saat gagal (contoh utama)

{ "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, coba lagi" }, "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 } ] } ] }

Guard memeriksa apakah saldo mencukupi terlebih dahulu, lalu memotong sebelum panggilan eksternal. Pemotongan itu memasang kunci optimistis (CAS) pada sys.version milik wallet. Jika eksekusi lain mengubah wallet di antara pembacaan saldo dan pemotongan, eksekusi di-abort karena versi tidak cocok dan catch mengembalikan 409. Karena tidak ada panggilan eksternal yang dilakukan, permintaan bersamaan tidak terkena pemotongan ganda. Hanya setelah pemotongan dipastikan, LLM baru dipanggil, dan jika panggilan itu gagal, catch menambahkan kembali jumlah yang dipotong (cost) untuk melakukan refund (kompensasi), lalu mengembalikan 502. Urutannya adalah memastikan penagihan sebelum panggilan eksternal yang tidak dapat dibatalkan, lalu melakukan kompensasi hanya saat gagal. Kunci rahasia diletakkan pada header secret:true. Untuk batasan kompensasi, lihat Tanpa transaksi dan kompensasi di Semantik Eksekusi.

8. Menjadikan gambar (URL) sebagai Media lalu melampirkannya ke Content

{ "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 }" } } } ] }

Buat Media dengan sebuah name, dan ResourceCreate (Content) menaruh { /img/sys/id } ke dalam field referensi. Media memakai model fields yang sama dengan Content. file adalah direktif ingest { source, encoding }. Karena ini merupakan ingest file, mode-nya Async.

9. Gambar base64 menjadi 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. Publikasi atau penghapusan bersyarat setelah moderasi

{ "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 saat kegagalan eksternal

{ "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": "Pembuatan gagal" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ] }

Paralel

12. Menggabungkan 2 panggilan eksternal paralel menjadi Content

{ "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. Peninjauan pendaftaran: skor paralel, lalu keputusan berbasis and

{ "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 } ] } ] }

Anda juga dapat menyisipkan { /ptr } ke dalam jalur URL. Hasil branch dirujuk setelah join. Ada 2 panggilan eksternal (maksimal 3).

Perulangan dan agregasi

14. N Content dari input array (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): menyemai slot

{ "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 mencakup from hingga to, inklusif (literal integer, step default-nya 1). as mengikat penghitung saat ini ke { /i }.

16. Penghapusan cascade (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 }" } } } ] }

Untuk lebih dari 100, tangani dengan 18. Menelusuri seluruh halaman.

17. Akumulasi loop: penjumlahan SetVar

{ "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. Menelusuri seluruh halaman

{ "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, dan SetVar menelusuri setiap halaman. Karena panggilan eksternal dilarang di dalam body Loop, di sini hanya digunakan operasi resource.

19. Mengumpulkan id secara batch dari daftar email (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 }" } } } ] }

Pembacaan (ResourceFind) bukan panggilan eksternal, sehingga diizinkan di dalam body Loop. Keberadaan dan ketiadaan masing-masing diakumulasikan dengan merge.

Saga dan konkurensi

20. Saga pembayaran (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 }" } } } ] } ] }

Setelah memesan (draft): saat pembayaran berhasil, ia mengonfirmasi, mempublikasikan, dan mengembalikan 201; saat gagal, catch menghapus pemesanan (kompensasi) dan mengembalikan 402; finally selalu mencatat log. Kompensasi berbasis penghapusan menghasilkan sys.id yang baru, sehingga termasuk dalam batasan di mana referensi menjadi rusak (lihat Tanpa transaksi dan kompensasi di Semantik Eksekusi).

21. Kunci optimistis 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, coba lagi" }, "isError": true, "statusCode": 409 } ] } ] }

Baca stok untuk memperoleh sys.version terbaru, lalu potong dengan versi itu (version). Jika eksekusi lain mengubah nilai di antara pembacaan dan penulisan, eksekusi di-abort karena versi tidak cocok dan catch mengembalikan 409. Guard kehabisan stok berada di luar Try (pengembalian dini yang normal).