Skip to content

Workflows: overview

A workflow is a state machine that drives approvals and automation over your data. You author it once in a *.workflow.xml file (or draw it in the visual designer), and the engine walks a record through a graph of nodes connected by transitions — pausing for human approvals, firing notifications, running SQL, and stamping a business status as it goes.

Workflows are versioned and company-scoped, and every run is bound to a single entity — most often a wizard’s submitted form response, or a row created by a view.

A workflow is a directed graph. Nodes are the states; transitions are the edges between them.

┌─────────────────────────┐
│ Start │
└────────────┬────────────┘
│ Submit
┌─────────────────────────┐
│ Action · notify Purch. │
└────────────┬────────────┘
│ ToReview
┌─────────────────────────┐
│ Approval · review │
└──────┬───────────┬──────┘
Approved Rejected
▼ ▼
┌─────────────────────────┐
│ End │
└─────────────────────────┘
Prefer a picture?

The engine recognises seven node types (the NodeType enum). Only some pause the flow; the rest auto-advance.

Type Purpose Advances
Start Entry point; every workflow needs exactly one. Auto — fires its single outgoing transition on start.
Approval Waits for one or more people to approve/reject. Pauses until the approval gate is satisfied.
Action Runs a side effect — a notification or SQL. Auto — executes, then follows its single outgoing transition.
Decision Branches on transition Expressions. Auto — takes the first passing outgoing transition (by Order).
Assignment Sets the instance’s assigned role/user. Auto.
Standard A generic waypoint / manual task. Waits for an external transition.
End Terminal; marks the instance Completed. Terminal.

A transition carries From, To, an optional human Label, an optional guard Expression, an Order (for Decision branching), a RequireComment flag, and an optional FlowStatus. See Authoring a workflow for the full element reference.

Every instance tracks two independent status fields:

  • Lifecycle Status — the engine’s own state: Active, Completed, or Cancelled.
  • Business FlowStatus — a free-text label your workflow chooses (e.g. Awaiting Purchasing Approval, Approved, Rejected). It is sticky: a transition only overwrites it when it carries a non-empty FlowStatus, so it persists across transitions that leave it blank. It shows in the Workflow Instances grid and is available to guards as @FlowStatus and to notification templates as {{FlowStatus}}.

An instance is always bound to one entity — (EntityType, EntityId). There are two hooks that start or drive a workflow, both authored on the source side (the wizard or view), not in the workflow XML:

  1. Wizard Terminal handoff — a wizard’s Terminal node names a WorkflowName and starts it bound to WizardFormResponse, passing the wizard’s collected fields (and any SetVariable values) as the workflow context.
  2. View hooks — a table/form view can declare <WorkflowStartup> (start a workflow after a successful insert, over the new row) or <WorkflowTransition> (fire a transition after a submit). See SQL & view hooks.

The starting context becomes the instance’s ContextData (JSON). Every @variable in it is visible to transition expressions, approval resolution, action SQL parameters, and notification templates.

  • Approvals create pending records that approvers act on through the built-in Pending Approvals list and the WF_ApprovalForm — no custom form needed. The ApprovalConfig Type (Any / Sequential / Parallel) decides when the gate is satisfied. See Engine, approvals & SLA.
  • SLA blocks (<Sla TimeoutMinutes Escalation ReminderMinutes>) declare deadlines on Approval nodes and a background hosted service polls for breaches. See the same page for the current implementation status.
<Workflow Name="ProductApproval" Slug="product-approval" Label="Product Approval"
EntityType="WizardFormResponse" Category="Inventory">
<Nodes>
<Node Key="Start" Type="Start" Label="Start">
<Position X="30" Y="20" Width="140" Height="80" />
</Node>
<!-- Action node: fires an in-app notification, then auto-advances -->
<Node Key="NotifyPurchasing" Type="Action" Label="Notify Purchasing">
<Position X="10" Y="200" />
<ActionConfig>
<ActionType>AppNotification</ActionType>
<Payload><To>Role:Purchasing</To>
<Message>New product request "{{ProductName}}" is awaiting review.</Message></Payload>
</ActionConfig>
</Node>
<!-- Approval node: pauses until a Purchasing member decides -->
<Node Key="PurchasingReview" Type="Approval" Label="Purchasing Review">
<Position X="360" Y="200" />
<Sla TimeoutMinutes="1440" Escalation="Notify" ReminderMinutes="720" />
<ApprovalConfig>
<Type>Any</Type>
<Approvers>
<Approver Type="Role" Value="Purchasing" />
</Approvers>
<OnApprove Transition="Approved" />
<OnReject Transition="Rejected" />
</ApprovalConfig>
</Node>
<Node Key="End" Type="End" Label="Done">
<Position X="1030" Y="370" Width="160" Height="80" />
</Node>
</Nodes>
<Transitions>
<Transition From="Start" To="NotifyPurchasing" Label="Submit" Order="1" FlowStatus="Pending Review" />
<Transition From="NotifyPurchasing" To="PurchasingReview" Label="ToReview" FlowStatus="Under Review" />
<Transition From="PurchasingReview" To="End" Label="Approved" FlowStatus="Approved" />
<Transition From="PurchasingReview" To="End" Label="Rejected" RequireComment="true" FlowStatus="Rejected" />
</Transitions>
</Workflow>

The full worked samples are ProductApproval.workflow.xml and PurchaseOrderApproval.workflow.xml in sample/Inventory/models/workflows/.