Special Actions — Delay, Rate Limit, Code Condition, Set Variables, Split, Naming

Download skillThe skill is a reference md and part of wix-manage skill. You can use the following command to add the full wix-manage skill to your project:
Copy

TL;DR

  • DELAY: EITHER relative (offsetExpression: "{{N}}" number literal + offsetTimeUnit) OR absolute (dueDateExpression). Never both.
  • RATE_LIMIT: only as THE root action, only one, maxActivationsExpression: "{{1}}", for new steps, identifier = var() of a trigger contact/visitor identity field; preserve existing newer identifiers (§2).
  • CODE_CONDITION (full spec Automations Conditions §5): JavaScript export default function (payload) { return <boolean>; }, the fallback for logic the condition panel can't render; dynamicVariableExpressions MUST list every payload field the code reads — the runtime supplies ONLY those.
  • SET_VARIABLES and SPLIT are alpha: not yet in the public API docs, but supported by the builder. setVariablesInfo / splitInfo are not in the public schema, so after Create/Update read the automation back and confirm the node still has its *Info — if the public API dropped it from an automation you just created in this task, tell the user and, with their OK, delete that new automation and rebuild it with a fallback (duplicate steps instead of SPLIT; formulas in the consuming fields instead of SET_VARIABLES). On an update of an existing automation, never delete or rebuild the user's steps yourself: stop, report what was dropped, and ask.
  • Code variables (wix_automations-data_manipulation_code) can't be created through the public API (§4).
  • Timing values in {{N}} (delay offsets, rate-limit values) must meet the panels' whole-number limits. Reject {{0.5}}, {{.5}}, {{01}} and {{ 3 }} for both relative delays and rate-limit durations; convert fractional durations into supported smaller units (Automations Mapping Expressions §1).
  • No MERGE. SPLIT paths never re-join.
  • Every node: shape rules from Automations Graph and Data Model (own *Info only, [] not omitted, namespaces per §3 there). Expression syntax: Automations Mapping Expressions.

1. DELAY

Pauses the flow before the next step. Place it anywhere except before the root RATE_LIMIT.

Relative — wait N units after the previous step finishes:

Copy

Absolute — wait until a date/time:

Copy

MUST (builder + API):

  • Exactly one mode. Relative ⇒ omit dueDateExpression. Absolute ⇒ omit offsetExpression and offsetTimeUnit.
  • offsetExpression is a whole-number literal in braces ("{{3}}") — no var(), no formula, no decimals, never bare "3". Anything else loads in the panel as 1.
  • offsetTimeUnit ∈ MINUTES | HOURS | DAYS | WEEKS | MONTHS (no seconds). Never write UNKNOWN_TIME_UNIT on a DELAY — but an absolute-mode DELAY reads back with offsetTimeUnit: "UNKNOWN_TIME_UNIT" filled in by the server; that is expected, don't "fix" it. Panel limits: MINUTES/HOURS 1–999, DAYS 1–365, WEEKS 1–52, MONTHS 1–12 — convert larger values to a bigger unit.
  • dueDateExpression (≤1000 chars) is ONE of: a bare ISO date-time literal with offset, NOT in braces ("2025-08-25T13:57:00+03:00" — the builder's own format), "{{var(\"<date-time field>\")}}", or a single date-time formula (addToDate(date;years;months;days), subtractFromDate(...), now()). The field must exist in this node's aggregated schema. A due date already in the past runs the next step immediately.
  • postActionIds ≤1 id; [] ends the flow. To fan out after a delay, point it at a SPLIT.

Choosing:

  • "2 hours after signup" → relative. "24 h before the workshop" → absolute with subtractFromDate.
  • "Run the whole automation X BEFORE the event date" is not a delay — it is the trigger's scheduledEventOffset (Automations Schemas and Scheduling §5). Delays only move execution LATER.
  • "Send now AND remind before the event" → first step, then an absolute DELAY on the event start (subtractFromDate(var("start_date");0;0;1)), then the reminder. A trigger offset would delay the first step too.
  • A delay does not change what data is available, but payload values are captured at trigger time and can go stale across long waits.
  • Delays on duplicated branches: see the delay-arithmetic rule in Automations Graph and Data Model §2.

2. RATE_LIMIT ("run once per … every …")

Stops the run when the same identity already triggered it within the window; otherwise continues to its child.

Copy

MUST:

  1. It is rootActionIds[0] and its postActionIds[0] is the rest of the flow. Never under a condition, delay, split, or any other step (the builder shows it as the trigger's "frequency" footer; anywhere else the node spins forever). At most one per automation.
  2. maxActivationsExpression: "{{1}}". The builder models only "once every X" and rewrites any other value to {{1}} as soon as the user opens the panel — never promise "N times per X".
  3. For a NEW rate-limit step, uniqueIdentifierExpression = exactly {{var("<path>")}} where <path> is a field of the TRIGGER payload schema (any object depth, not inside an array) annotated identityType: "contact" or "visitor" for compatibility with both frequency panels. Not enriched props (contact.email), not action outputs, not computed or static values (the footer disappears). If the trigger has no such field, do not claim rate limiting is impossible: the newer panel also supports string member/user identity fields and string format: uuid fields without an identityType. Availability of that creation UI varies; ask the user to configure the frequency in their builder instead of guessing support. On UPDATE, preserve an existing identifier of these newer types and its expression exactly; do not replace it with contact/visitor, remove the RATE_LIMIT, or treat it as invalid merely because it is outside the conservative creation rule. Confirm its path still exists in the trigger schema; a missing path needs resolution before saving.
  4. Duration: rateLimitDurationExpression: "{{N}}" (whole number) + rateLimitDurationTimeUnit ∈ MINUTES | HOURS | DAYS | WEEKS | MONTHS, same per-unit limits as DELAY. "Only ever once" (the panel's unchecked "reset every" box) — only when the user explicitly wants it — is: no rateLimitDurationExpression and rateLimitDurationTimeUnit: "UNKNOWN_TIME_UNIT"; this is the one place UNKNOWN_TIME_UNIT is written (the builder writes exactly this shape). Use {{24}} HOURS, not {{1440}} MINUTES (the builder rewrites that one on load).

Recipes: once per contact per day → {{1}}, {{24}} HOURS, contact field. Once per contact ever → {{1}}, no duration expression, UNKNOWN_TIME_UNIT.

3. CODE_CONDITION

The fallback for logic the visual condition panel can't render (array indexing, per-element math, string splitting, date parts, regex). Must-know: dynamicVariableExpressions MUST list every payload field the code reads (the runtime supplies ONLY those); the builder's JSDoc header is required; code ≤ 1000 chars. Full spec and examples: Automations Conditions §5; when to use it: Automations Conditions §4.

4. SET_VARIABLES (alpha)

Computes named values for later steps.

Copy

MUST:

  1. namespace is the literal setVariable. Downstream: {{var("setVariable.8a9e4f20-1b3c-4d5e-8f6a-7b8c9d0e1f2a")}} (the tax amount).
  2. outputMapping and outputSchema.properties have exactly the same keys (a mapping key without a property is dropped on save). Keys are opaque, stable ids: prefer a fresh uuid v4 per key — what the builder writes for new variables; descriptive keys also load and round-trip, but get a "key should be a uuid" warning. Keys must be unique across ALL variable steps. Downstream: setVariable.<key>.
  3. For new variables use {type, title, format?}: type ∈ string | number | boolean, format ∈ date | date-time | email | uri. title is REQUIRED (the visible name; a title-less variable is invisible downstream and dropped on save) and MUST be unique across ALL variable and code-variable steps in the automation (the panel refuses duplicate names). Add "required": [<every key>] to outputSchema — the panel writes it on save; other property fields such as description are stripped. Exception on UPDATE: preserve an existing wixCustomType: "IMAGE_URL" together with type: "string" and format: "uri"; it distinguishes an image variable from a general URL. The builder preserves this metadata even when its image-variable picker is unavailable. Do not normalize it away or rename its stable variable key.
  4. An expression reads ONLY this node's aggregated schema — never another variable defined in the same step. Repeat an expression only when the user has not requested a later step or a dependency on a saved value. For a requested sequential dependency, create two connected SET_VARIABLES nodes: first stores the value; second reads the first node's key. Both use namespace setVariable, with distinct stable keys and titles. If Validate reports a missing same-step variable, move the dependent mapping to the second node; do not replace that dependency with a fresh calculation from the trigger.
  5. Panel-renderable values only: plain text / literals, {{var(...)}}, and functions from the builder list in Automations Mapping Expressions, using eq/gt/gte/lt/lte for NUMBERS only (they reject string and boolean arguments — for a boolean use the value itself or not(...), for strings stringEq); no unclosed {{. A number/boolean variable holds a JSON literal of that type or exactly one whole {{…}} — never plain text. NEVER a formula in a date-time variable (the panel replaces it with "now"); a literal date-time is re-saved in the site timezone. Anything else → the §4 alternatives (code variables can't be created publicly).
  6. Place it BEFORE a condition when both branches need the value.

Sequential dependency example: SET_VARIABLES A → SET_VARIABLES B → consuming action. A stores a numeric value under fresh UUID key <key-a> using the verified trigger field. B stores a numeric tenth of that saved value under another fresh UUID key <key-b>, with outputMapping["<key-b>"] = '{{divide(var("setVariable.<key-a>");10)}}'. For each node, include its own key in outputSchema.properties and required, with the requested title and type: "number". A's postActionIds points to B, and B's points to the consumer. Keep both nodes and B's reference to A's key in the saved read-back; a single node that computes both values independently does not preserve the requested sequence.

Code variables are not available through the public API. The builder's code-variable step (APP_DEFINED wix_automations-data_manipulation_code, namespace: "setVariable") is not in Resolve Actions, and Create/Update refuse an automation containing a new one (PermissionDenied "Unauthorized automation creation or update") even when Validate says VALID. For a value the panel can't express (regex, splitting, day of year, joining a list, time between dates), in this order: a formula in the consuming field or a SET_VARIABLES step, if the §2 function list covers it; a CODE_CONDITION when the value only decides a branch (it creates fine); a "Generate or analyze text" (wix_automations-llm_call) step with an override output schema to extract or compute a text value; otherwise save without it and tell the user to add a code variable in the builder. An existing code variable in an automation you update: leave it untouched and keep its keys.

Variables are transformation, not extraction: they cannot pull one element out of an unindexable array. A code condition can inspect the declared array to decide a branch. For a scalar needed downstream, first find wix_automations-llm_call with Resolve Actions, then read the selected site's Get Runtime Action response's action.inputSchema (Automations API Catalog §4). Use that LLM text action only after verifying how this schema accepts all required source data: a scalar prompt needs a verified serialized-text source, not a raw array-returning expression, an invented context field or an assumed array-to-string conversion. If no supported input or scalar source exists, explain that specific blocker. Do not add extraction for an initializer field whose own contract explicitly supports array indexes.

5. SPLIT (alpha) — parallel paths

Every path ALWAYS runs, in parallel. It is not a condition.

Copy

MUST:

  • 2–10 paths; ≤10 SPLIT nodes per automation. A SPLIT has only splitInfo — no postActionIds.
  • Path ids: fresh uuid v4, unique against every action id, every other path id, and the trigger key. Paths are NOT entries in configuration.actions.
  • name 1–100 chars is the label; optional displayName ≤60 overrides it (the builder sets it only on rename — same text is fine). postActionId = first action of that path, or omitted for an empty path.
  • Each path's chain is ordinary actions with one successor each; no action reachable from two paths.
  • Placement: parallel work right after the trigger → the SPLIT is the root (unless a RATE_LIMIT is root; then the SPLIT is its child). Later → the previous step points to it.
  • No re-join. A step that must run after all paths finish can't exist — put it before the SPLIT or on one path.

Editing an existing SPLIT: add a branch by appending one path (new id, new first action); remove one by deleting that path plus every node below it; if one path would remain, delete the SPLIT and connect its parent (or rootActionIds) straight to that path's first action — the builder does the same. Never linearize paths into a sequence — that changes behavior. Adding a SPLIT in the builder UI may be feature-gated for the user, but drawing and editing an existing one always works.

6. Why there is no MERGE

The API defines MERGE + conditionInfo.mergeActionId, but the builder cannot draw them. To let both condition branches continue into shared steps, DUPLICATE those steps into each branch (Automations Graph and Data Model §2).

7. Display names

  • Sentence case, 1–60 chars, specific to purpose in context: "Send welcome email", not "Send email".
  • APP_DEFINED: action + purpose ("Send welcome email"). DELAY: the wait ("Wait 2 hours before follow-up"). CONDITION / CODE_CONDITION: what's checked ("Check if VIP customer"). SET_VARIABLES: what's stored ("Set customer tier"). SPLIT path: the branch's purpose.
  • Existing site-action (API integration) nodes: don't set or change their displayName.

Last updated: 12 November 2026

Did this help?