Language Rules
WOML is an XML-like language with deliberate raw-content handling for JavaScript and inline JSON Schema. The compiler rejects ambiguous or unsupported structure instead of guessing what the author meant.
Documents
Every file has one <woml> root and one profile: runnable workflow, reusable step, or reusable notification provider. <woml> has no attributes.
<woml>
<imports>
<module name="pricing" from="./pricing.ts" />
</imports>
<workflow id="calculate-order" name="Calculate order" version="1.0.0">
<triggers>
<manual id="start" />
</triggers>
<steps>
<step id="calculate">
<script>return pricing.total(context.payload.items);</script>
</step>
</steps>
</workflow>
</woml><props> belongs only to reusable documents. A runnable <workflow> cannot declare component props because its input comes from context.payload.
Identifiers
Workflow IDs use lowercase kebab case such as process-order. Trigger, step, control-flow, and executable IDs use lower camel-compatible JavaScript-safe form such as calculateTotal. IDs participating in execution share a workflow-wide namespace.
That shared namespace makes durable events and references unambiguous. A branch cannot reuse an ID merely because it appears visually nested.
Values
Durations use a positive number and ms, s, m, h, or d. Rates use count/duration. Durable values are JSON-compatible and finite.
Examples include 250ms, 30s, 10m, 24h, and 7d. A rate such as 100/1m means a positive count over one duration.
Script results, service arguments, workflow payloads, and durable state must cross a JSON boundary. Do not return functions, symbols, cyclic objects, or NaN.
Raw scripts
Write ordinary JavaScript directly inside <script> without CDATA or a function wrapper. Script syntax that contains a literal closing </script> sequence terminates the body.
<script>
const total = context.payload.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return { total };
</script>WOML preserves source positions so JavaScript failures can point back to the original file. Because the body is raw, XML escaping rules do not turn ordinary JavaScript into CDATA ceremony.
References
Declarative references use exact dot paths without whitespace, brackets, calls, operators, optional chaining, or fallbacks.
items="{{context.steps.loadOrders.orders}}"
test="{{context.steps.validate.approved}}"
from="{{context.steps.calculate.total}}"Expressions such as {{context.steps.a.x || 0}}, bracket access, or function calls are not reference syntax. Put computation in a <script> step and return a named result.
References are also graph-checked. The producer must dominate the consumer: every valid route to the consumer must complete the producer first.
Scripts and declarative attributes
Use JavaScript for transformation and local logic inside one executable node. Use tags for durable workflow structure: retry, parallelism, routing, loops, forks, approvals, and lifecycle. A JavaScript for loop is useful for in-memory transformation; <for-each> is useful when every item needs durable identity, attempts, progress, and inspection.
Element placement
The grammar validates elements by their semantic parent. <when> belongs to <choose>, <case> belongs to <switch>, and <branch> belongs to <fork>. Lifecycle hooks belong inside <lifecycle>, not inside ordinary steps.
Workflow-level singleton sections are not meant to depend on an arbitrary visual order. Keep the conventional config, triggers, steps, lifecycle order for readability, while relying on validation rather than accidental parser order.
Schemas
<schema> contains inline JSON Schema for supported webhook and event triggers. The payload must validate before WOML creates a run. External schema-file references are not part of v1.
Versioning
The workflow version attribute describes the author's workflow version. WOML's compiled model, event vocabulary, and host protocols are versioned internal contracts; authors do not place a woml-version attribute in source.