Statement Catalog
Last updated: July 23, 2026
Each element of the statements array is a single statement. This document catalogs the fields, behavior, and result of all 17 statement types. Every value slot follows the rules of Value Expressions (reference, literal, JsonLogic, locale map).
Statement summary
| Category | type | One-line summary |
|---|---|---|
| Resource writes | ResourceCreate | Create Content/Media (optionally publish) |
ResourceUpdate | Full replacement of Content/Media fields (any field/locale not provided is deleted) | |
ResourcePatch | Partial merge of Content/Media fields (only the specified fields/locales; a literal null deletes) | |
ResourceDelete | Delete (Draft/Archived only; if Published, unpublish first) | |
ResourcePublish / ResourceUnpublish | Publish / unpublish | |
ResourceArchive / ResourceUnarchive | Archive / unarchive | |
| Resource reads | ResourceRead | Read a single item by id |
ResourceFind | First matching single item by filter (null if none) | |
ResourcePageRead | Filter / sort / page read ({ items, next }) | |
| External | Http | External HTTP call ({ status, body }). Async only |
| Variables | SetVar | Declare / update a script-scoped variable |
| Control flow | If | Conditional branch |
Loop | Iterate (foreach / while / counted) | |
Parallel | Run branches concurrently | |
Return | Return a result and terminate early | |
Try | Exception handling (catch/finally) |
Cyclic calls are capped at 3. The resource-write statements above (
ResourceCreate,ResourceUpdate,ResourcePublish, etc.) raise change events, and those events can run a Script again through a Webhook. Such a chain (Script → event → Webhook → Script → …) continues at most 3 times; beyond that the platform drops it automatically to prevent infinite loops.
Common fields
{ "type": "<StatementType>", "name": "<optional, unique within script>", /* ...type-specific fields... */ }type: The discriminator. One of the values in the table above (required).name: Optional. When set, the result is bound into the context at/<name>, so later statements can reference it as{ /<name>/... }. Omit it if you don't use the result.- Binding-name rules:
name(andLoop'sas) is a key laid directly onto the context root, so it is validated on save. It must not be an empty string, must not contain/or~(so it can be used as a JSON Pointer key), must not equal a reserved root (payload,vars,error), and must be unique within a single Script. On a violation, the save is rejected withWGL400033(format),WGL400032(reserved word), orWGL400034(duplicate), respectively.
Entity reference shape
Entity references such as contentType and target are unified into a single shape: { "sys": { "id": <value expression> } }. Only sys.id is needed; the target type is inferred from resource (sys.type and sys.targetType are omitted).
contentType.sys.idis usually a literal (e.g."ct_post").target.sys.idis usually a{ /ptr }value expression (resolved at runtime; e.g.{ /payload/sys/id }).
resource
Resource-family statements specify the target kind with resource: "Content" | "Media".
Resource writes
Every write statement has propagateEvents (default false). Setting it to true makes that write emit its own EntityEvent (triggering downstream work such as search indexing and Webhooks). The default does not emit (a quiet system write).
ResourceCreate
Creates a Content or Media. Content and Media share the fields model, and values are locale maps.
| Field | Applies to | Description |
|---|---|---|
resource | Common | "Content" or "Media" (required) |
contentType | Content | The Content Type to create ({ sys: { id } }). Required for Content |
fields | Common | Field map { "<field>": { "<locale>": value } }. Each populated field requires the default locale bucket. Content keys follow the Content Type definition; Media keys are fixed (title, description, file) |
locale | Common | (Convenience) When provided, each value in fields is auto-wrapped as { <locale>: value } |
publish | Common | Publish after the write (exposed on CDA/ACDA). Default true |
Mediafile: Thefields.file.{locale}value is an ingest instruction{ "source": <value expression>, "encoding": "url"|"base64" }(both required). A Media write that includes a file is Async only (the engine processes it inline in the background, then publishes; common to both url and base64). You can also create a fileless Media. Ifpublish:truebut there is no file or processing is incomplete, the publish step errors; ifpublish:false, it staysDraft.- Result (
namebinding): The created resource.{ /<name>/sys/id },{ /<name>/fields/<field>/<locale> }.
// Content
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" } }, "publish": true, "name": "post" }
// Media. file is an ingest instruction (Async only)
{ "type": "ResourceCreate", "resource": "Media",
"fields": {
"title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } }
}, "name": "img" }ResourceUpdate
Fully replaces the fields of the target Content or Media (PUT). Whatever you pass in fields becomes the new set of fields, and any field and locale not present here is removed. To change only part of it, use ResourcePatch.
| Field | Description |
|---|---|
resource | "Content" or "Media" |
target | The target ({ sys: { id } }, required). The id is usually { /ptr } |
fields | The complete set of fields to write. Values are locale maps. Because this is a full replacement, any field and locale not present here is removed. For Media, file is an ingest instruction (see ResourceCreate above); listed files are always re-ingested, and files for locales not provided are deleted |
locale | (Convenience) Auto-wraps fields |
version | (Optional) A value expression (Int). Optimistic locking. When provided, the update runs only if it matches the target's current sys.version; on a mismatch it aborts with a version-conflict error (catchable with Try). If omitted, there is no check (last-write-wins) |
publish | Republish after the update. Default true |
Using Update to change only a Media's metadata drops the file entirely (because it is a full replacement). For a partial change, always use ResourcePatch. An Update that includes a file is Async-only.
{ "type": "ResourceUpdate", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "title": { "en-US": "Hello", "ko-KR": "안녕" }, "status": { "en-US": "published" } } }ResourcePatch
Partially merges the fields of the target Content or Media (PATCH). It overwrites only the fields (and the locales within them) you pass in fields, and leaves any field and locale you don't mention untouched. The value shape, locale, version, and publish are the same as ResourceUpdate.
| Field | Description |
|---|---|
resource | "Content" or "Media" |
target | The target ({ sys: { id } }, required). The id is usually { /ptr } |
fields | The fields to overwrite. Values are locale maps. Updates only the specified fields and locale buckets (the rest are kept). If a value is a literal null, that (field, locale) is deleted. For Media, file is an ingest instruction (see ResourceCreate above) |
locale | (Convenience) Auto-wraps fields |
version | (Optional) Same as ResourceUpdate (optimistic locking) |
publish | Republish after the update. Default true |
- Delete a specific locale or file: Give a literal
nullas the value. For example:"title": { "fr-FR": null }(deletes the fr-FR title),"file": { "en-US": null }(deletes the en-US file). A value expression that evaluates to null at runtime is not a deletion but an error (only a literal null deletes). - Giving an ingest instruction to a Media
filereplaces that locale's file (Async-only). If you don't provide a file, it is kept.
// +1 to viewCount(en-US) only. title, other locales, and everything else are kept as-is
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } }ResourceDelete
Deletes the target. Only the Draft and Archived statuses can be deleted. If it is Published or Changed the delete is rejected, so you must ResourceUnpublish first (for Media, it is also rejected while the file is being processed (busy)). It does not auto-unpublish (same as CMA/ACMA).
| Field | Description |
|---|---|
resource | "Content" or "Media" |
target | The target ({ sys: { id } }, required) |
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }ResourcePublish, ResourceUnpublish, ResourceArchive, ResourceUnarchive
Independently control the target's publish and archive state. All four share the same fields. The status precondition for each operation is the same as CMA/ACMA (publish is not allowed from Archived and requires file processing to be complete; unpublish only from Published/Changed; archive only from Draft; unarchive only from Archived).
| Field | Description |
|---|---|
resource | "Content" or "Media" |
target | The target ({ sys: { id } }, required) |
version | (Optional) A value expression (Int). Optimistic locking. When provided, the operation runs only if it matches the current sys.version |
{ "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceUnpublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } }
{ "type": "ResourceArchive", "resource": "Media", "target": { "sys": { "id": "{ /m/sys/id }" } } }Resource reads
Read statements do not change state (no propagateEvents).
All three read statements use from (optional, default Current) to choose which stored version to read. Current is the latest draft that the content studio sees (the value CMA/ACMA reads); Published is the published snapshot (the value CDA/ACDA delivers, as of the last publish).
In addition, ResourceFind and ResourcePageRead can enable Advanced Search via advanced (optional, default false). It is Content-only, so it is ignored for Media reads. When on, where can use the regex, near, and within operators and full-text search on text (on a full-text-enabled LongText field, eq also matches items that contain the value, by partial and fuzzy matching), and order can sort by fields.*. When off, those three operators are rejected, eq on text is exact match, and prefix and the comparison and list operators work regardless of Advanced Search. A newly created or edited item takes a short moment (about 1 second) to be reflected in Advanced Search, so it may be missed by the immediately following Advanced Search query. To read a newly written item right away, use ResourceRead by id (the primary store, with no reflection delay) or query by the sys.id the write returned.
In where and order, write a content field as fields.<field> (the bare name alone is not recognized). For fields.<field>, the space default locale is automatically applied, so you do not attach a locale yourself. The fields.status and fields.slug in the examples below are themselves default-locale lookups. Only when you want to target a specific (non-default) locale do you state it explicitly as fields.<field>.<locale> (e.g. fields.title.ko-KR). sys.* (such as sys.createdAt) and createdBy (:self) are written as-is, without fields.. The full rules are in Locales in where and order in Value Expressions.
ResourceRead
Reads a single item by id (get-by-id). The result binds the entire resource to the name.
| Field | Description |
|---|---|
resource | "Content" or "Media" |
target | The target ({ sys: { id } }). The id is a value expression |
from | (Optional) Current (default, the latest draft) or Published (the published snapshot) |
- Result: Reference
{ /<name>/sys/id }and{ /<name>/fields/<field>/<locale> }directly (noitems/0needed). - If the target does not exist, it errors. You can wrap it in
Tryto handle that.
{ "type": "ResourceRead", "resource": "Content",
"target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" }ResourceFind
Reads the first matching single item by filter. If there is none, it is null. Use it to find one record by a unique business key (slug, email, sku).
| Field | Description |
|---|---|
resource | "Content" or "Media" |
contentType | (Content) The Content Type to search within ({ sys: { id } }) |
where | The filter ({ "<field>": { "<op>": <value> } }). Operators are those in the operator list (regex/near/within require advanced). createdBy: ":self" supported |
order | The sort that determines the "first" one when multiple match (e.g. "-sys.createdAt") |
from | (Optional) Current (default, the latest draft) or Published (the published snapshot) |
advanced | (optional) Run via Advanced Search. Content-only (Media ignored). Default false. See the Resource reads note above. |
- Result: Binds the first matching resource to the name. Reference it directly as
{ /<name>/fields/<field>/<locale> }. Since it isnullwhen there is none, branch on existence with{ "==": [ "{ /<name> }", null ] }(the typical find-then-upsert pattern).
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" }ResourcePageRead
A filter, sort, and page read.
| Field | Description |
|---|---|
resource | "Content" or "Media" |
contentType | (Content) The Content Type to search within |
where | The filter ({ "<field>": { "<op>": <value> } }). Operators are those in the operator list (regex/near/within require advanced). createdBy: ":self" supported |
order | The sort (e.g. "-sys.createdAt") |
limit | Page size (100 or fewer) |
cursor | For the next page, the next from the previous result |
from | (Optional) Current (default, the latest draft) or Published (the published snapshot) |
advanced | (optional) Run via Advanced Search. Content-only (Media ignored). Default false. See the Resource reads note above. |
- Result:
{ items, next }.{ /<name>/items/0/... }, and the next page is{ /<name>/next }. - To iterate over everything, use
Loop while "{ /vars/hasMore }"together withcursorandSetVaraccumulation (see the Cookbook).
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "order": "-sys.createdAt", "limit": 100, "name": "page" }External
Http
Calls an external HTTP endpoint. When Http is present, executionMode must be Async (ExternalIo).
| Field | Description |
|---|---|
method | "GET", "POST", "PUT", "PATCH", "DELETE" |
url | The target URL (a value expression; { /ptr } can be interpolated) |
headers | [{ "key", "value", "secret"? }]. value is a value expression. A secret:true header is treated as CMA (administrator) only: it is not exposed to end users and is decrypted only immediately before the request is sent |
body | The request body (a value expression or JSON) |
timeoutMs | The timeout for this call (ms) |
retry | The number of retries when the response status is 400 or higher. Default 0; the ceiling is maxHttpRetry (default 2) |
ignoreStatusCode | Whether to treat this call as a failure when the final status (after retries) is 400 or higher. When false (default) it is treated as a failure and becomes a Try/catch target. When true it is not treated as a failure and { status, body } is bound as-is (the caller branches on status itself) |
- Result:
{ status, body }.{ /<name>/status },{ /<name>/body/... }. - Response size limit: The response body is at most 10MiB. If it exceeds that, this call fails with an exception and can be handled like any other runtime failure with
Try/catch(this is a size-based failure, so it is not suppressed byignoreStatusCode).
{ "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, "retry": 1, "name": "resp" }Variables
SetVar
Declares or updates a script-scoped mutable variable. Reference it as { /vars/<var> } (JsonLogic has no variable declaration, so this is provided as a statement).
| Field | Description |
|---|---|
var | The variable name. Referenced as { /vars/<var> } |
value | A value expression. It can reference itself to accumulate |
{ "type": "SetVar", "var": "total", "value": 0 }
{ "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } // accumulate
{ "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /row/sys/id }" ] ] } } // collect into arrayControl flow
If
A conditional branch. condition is JsonLogic, and truthy/falsy follows the Truthiness rules.
| Field | Description |
|---|---|
condition | JsonLogic (evaluated as a boolean) |
then | The Statement array to run when true |
else | (Optional) The Statement array to run when false |
{ "type": "If",
"condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" } } ],
"else": [ /* ... */ ] }Loop
Iteration. Pick one mode: over (foreach), while (condition), or for (counted). In every mode the engine enforces an upper bound with maxIterations (to prevent infinite loops). External calls inside body (Http, Media file ingest) are forbidden.
| Field | Description |
|---|---|
over | foreach: a value expression that resolves to an array |
while | condition: JsonLogic (repeats while true) |
for | counted: { "from", "to", "step"? }. From from to to inclusive; step defaults to 1 |
maxIterations | The maximum iteration count enforced by the engine (required) |
as | The name to bind the current item or index to ({ /<as> }) |
body | The Statement array for the loop body |
// foreach
{ "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 }" } } } ] }
// while
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000, "body": [ /* ... */ ] }
// counted (1..10 step 2)
{ "type": "Loop", "for": { "from": 1, "to": 10, "step": 2 }, "as": "i", "maxIterations": 100, "body": [ /* ... */ ] }Parallel
Runs branches concurrently and continues after they join. References between branches are not allowed (if there is a dependency, place them sequentially).
| Field | Description |
|---|---|
branches | Statement[][]. Each element is one branch (an array of 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" } ]
] }Return
This is the return from ordinary programming. It returns the Script's result to the caller and terminates normally at that point.
| Field | Description |
|---|---|
value | (Optional) The value expression to return |
isError | Default false. When true, value comes back as the response's error (otherwise as return) |
statusCode | The response status code. Default 200 |
- If
Returnis never reached, there is no return value. To return a result, specifyvalueexplicitly. - Because it is a normal termination, not an exception or throw, it is not a
catchtarget (even insideTryit terminates the entire Script, butfinallystill runs). - A guard is also expressed with this statement:
Ifcombined withthen:[Return](return when the condition is violated, so what follows does not run). This is one of its several uses.
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 }
{ "type": "Return", "value": { "reason": "payment failed" }, "isError": true, "statusCode": 402 }Try
Exception handling.
| Field | Description |
|---|---|
body | The Statement array to attempt |
catch | (Optional) Runs when body fails. Exposes { message, statement } at /error |
finally | (Optional) Always runs regardless of success or failure |
- If
catchhandles it, the Script is not aborted. Only a failure with nocatchaborts the Script (including a compensation attempt). - What counts as a "failure", and the limits of compensation, are covered in Execution semantics, constraints, and security.
{ "type": "Try",
"body": [ { "type": "Http", "method": "POST", "url": "https://primary.api/gen", "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" } } } ],
"catch": [ { "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "Generation failed" }, "error": { "en-US": "{ /error/message }" } } } ],
"finally": [ /* always runs */ ] }Related documents
- Value Expressions: The value rules that all of the fields above follow.
- Execution semantics, constraints, and security: Execution order, errors, static constraints, and security.
- Cookbook: Complete examples that combine these statements.
- Script overview: The top-level structure and execution modes.
