Stage scope: this stage covers only what Build and Manage Wix Automations lists; follow the instruction attached to each guide marked "not yet published". "Stage" and "not yet published" describe how this skill is being rolled out; never mention them to the user. Explain a limit in product terms: what can't be done through the API here and where the user can do it instead (for example, in the automations builder).
TL;DR
offsetExpression: "{{N}}" number literal + offsetTimeUnit) OR absolute (dueDateExpression). Never both.maxActivationsExpression: "{{1}}", for new steps, identifier = var() of a trigger contact/visitor identity field; preserve existing newer identifiers (§2).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.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.wix_automations-data_manipulation_code) can't be created through the public API (§4).{{N}} (delay offsets, rate-limit values): whole numbers without leading zeros or spaces — the builder parser can't read 0.5, .5, 01 or {{ 3 }} (Automations Mapping Expressions §1).*Info only, [] not omitted, namespaces per §3 there). Expression syntax: Automations Mapping Expressions.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:
Absolute — wait until a date/time:
MUST (builder + API):
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:
subtractFromDate.scheduledEventOffset (Automations Schemas and Scheduling §5). Delays only move execution LATER.subtractFromDate(var("start_date");0;0;1)), then the reminder. A trigger offset would delay the first step too.Stops the run when the same identity already triggered it within the window; otherwise continues to its child.
MUST:
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.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".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.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.
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.
Computes named values for later steps.
MUST:
namespace is the literal setVariable. Downstream: {{var("setVariable.8a9e4f20-1b3c-4d5e-8f6a-7b8c9d0e1f2a")}} (the tax amount).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>.{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.{{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).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 can't pull one element out of an unindexable array — use an LLM text action or a code condition (§5 of Automations Feasibility and Planning (topic guide not yet published; consult the Automations API reference)).
Every path ALWAYS runs, in parallel. It is not a condition.
MUST:
splitInfo — no postActionIds.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.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.
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).
displayName.Last updated: 22 October 2026