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

CategorytypeOne-line summary
Resource writesResourceCreateCreate Content/Media (optionally publish)
ResourceUpdateFull replacement of Content/Media fields (any field/locale not provided is deleted)
ResourcePatchPartial merge of Content/Media fields (only the specified fields/locales; a literal null deletes)
ResourceDeleteDelete (Draft/Archived only; if Published, unpublish first)
ResourcePublish / ResourceUnpublishPublish / unpublish
ResourceArchive / ResourceUnarchiveArchive / unarchive
Resource readsResourceReadRead a single item by id
ResourceFindFirst matching single item by filter (null if none)
ResourcePageReadFilter / sort / page read ({ items, next })
ExternalHttpExternal HTTP call ({ status, body }). Async only
VariablesSetVarDeclare / update a script-scoped variable
Control flowIfConditional branch
LoopIterate (foreach / while / counted)
ParallelRun branches concurrently
ReturnReturn a result and terminate early
TryException 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 (and Loop's as) 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 with WGL400033 (format), WGL400032 (reserved word), or WGL400034 (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.id is usually a literal (e.g. "ct_post").
  • target.sys.id is 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.

FieldApplies toDescription
resourceCommon"Content" or "Media" (required)
contentTypeContentThe Content Type to create ({ sys: { id } }). Required for Content
fieldsCommonField 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)
localeCommon(Convenience) When provided, each value in fields is auto-wrapped as { <locale>: value }
publishCommonPublish after the write (exposed on CDA/ACDA). Default true
  • Media file: The fields.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. If publish:true but there is no file or processing is incomplete, the publish step errors; if publish:false, it stays Draft.
  • Result (name binding): 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.

FieldDescription
resource"Content" or "Media"
targetThe target ({ sys: { id } }, required). The id is usually { /ptr }
fieldsThe 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)
publishRepublish 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.

FieldDescription
resource"Content" or "Media"
targetThe target ({ sys: { id } }, required). The id is usually { /ptr }
fieldsThe 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)
publishRepublish after the update. Default true
  • Delete a specific locale or file: Give a literal null as 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 file replaces 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).

FieldDescription
resource"Content" or "Media"
targetThe 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).

FieldDescription
resource"Content" or "Media"
targetThe 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.

FieldDescription
resource"Content" or "Media"
targetThe 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 (no items/0 needed).
  • If the target does not exist, it errors. You can wrap it in Try to 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).

FieldDescription
resource"Content" or "Media"
contentType(Content) The Content Type to search within ({ sys: { id } })
whereThe filter ({ "<field>": { "<op>": <value> } }). Operators are those in the operator list (regex/near/within require advanced). createdBy: ":self" supported
orderThe 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 is null when 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.

FieldDescription
resource"Content" or "Media"
contentType(Content) The Content Type to search within
whereThe filter ({ "<field>": { "<op>": <value> } }). Operators are those in the operator list (regex/near/within require advanced). createdBy: ":self" supported
orderThe sort (e.g. "-sys.createdAt")
limitPage size (100 or fewer)
cursorFor 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 with cursor and SetVar accumulation (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).

FieldDescription
method"GET", "POST", "PUT", "PATCH", "DELETE"
urlThe 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
bodyThe request body (a value expression or JSON)
timeoutMsThe timeout for this call (ms)
retryThe number of retries when the response status is 400 or higher. Default 0; the ceiling is maxHttpRetry (default 2)
ignoreStatusCodeWhether 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 by ignoreStatusCode).
{ "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).

FieldDescription
varThe variable name. Referenced as { /vars/<var> }
valueA 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 array

Control flow

If

A conditional branch. condition is JsonLogic, and truthy/falsy follows the Truthiness rules.

FieldDescription
conditionJsonLogic (evaluated as a boolean)
thenThe 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.

FieldDescription
overforeach: a value expression that resolves to an array
whilecondition: JsonLogic (repeats while true)
forcounted: { "from", "to", "step"? }. From from to to inclusive; step defaults to 1
maxIterationsThe maximum iteration count enforced by the engine (required)
asThe name to bind the current item or index to ({ /<as> })
bodyThe 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).

FieldDescription
branchesStatement[][]. 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.

FieldDescription
value(Optional) The value expression to return
isErrorDefault false. When true, value comes back as the response's error (otherwise as return)
statusCodeThe response status code. Default 200
  • If Return is never reached, there is no return value. To return a result, specify value explicitly.
  • Because it is a normal termination, not an exception or throw, it is not a catch target (even inside Try it terminates the entire Script, but finally still runs).
  • A guard is also expressed with this statement: If combined with then:[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.

FieldDescription
bodyThe 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 catch handles it, the Script is not aborted. Only a failure with no catch aborts 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 */ ] }