Skip to content

Wizards overview

A wizard is a multi-step, guided data-entry flow. Where a form captures one record on one screen, a wizard walks the user through a graph of steps — collect some fields, branch on what they answered, run a bit of SQL, then finish — and can hand the result off to a workflow for review and approval.

Wizards are authored in *.wizard.xml as a node graph, edited visually in the in-app Wizard Designer, and run by the React Wizard Runner at /form/{slug}.

Reach for a plain view/form when the user edits one record on one screen. Reach for a wizard when the capture is inherently sequential or conditional:

  • the input spans several logical steps and you want a progress bar + review screen;
  • later steps depend on earlier answers (a Decision branch);
  • you need to run intermediate SQL between steps (create a draft row, look something up);
  • submission should start an approval workflow rather than just write a row.

A wizard is a set of nodes connected by links (directed edges). The runner starts at the entry node and follows links, pausing only where it needs the user. There are five node types:

Node Purpose Runner behaviour
Startup Initialise global variables. Auto-advances immediately.
Input Collect user data via fields. Renders a step and waits for Next.
Decision Branch on the collected values. Auto-advances down the first matching link.
Action Side effect: alert, run SQL, or set a variable. Auto-advances (an Alert pauses for acknowledgement).
Terminal Finish, and optionally hand off to a workflow. Submits the response and shows the outcome.

Only Input and Terminal (and an Alert action) stop the runner; Startup, Decision and the other Actions execute silently in an “auto-advance burst” between visible steps.

Everything the wizard knows is a variable. Input fields become variables (keyed internally as NodeId__FieldName, with a bare FieldName alias), Startup declares named globals, and an Action can compute new ones. Variables flow into:

  • Decision link expressions (which branch to take);
  • SQL blocks, as @FieldName / @VariableName substitutions (plus session parameters like @SessionUserId and @SessionCompanyId);
  • the saved response, and the workflow context on handoff.

<Link From="a" To="b" /> is an edge. A Decision node’s outgoing links carry an Expression; the first whose expression is true wins, with Expression="else" (or an expression-less link) as the catch-all. Expressions use the same expression engine as views.

The most powerful thing a wizard does is finish into a workflow. When a Terminal node declares a WorkflowName, submitting the wizard:

  1. saves a response row into the wizard’s own response table, Wizard.Resp{Name} (the collected values as JSON, plus a real column for every field marked Indexed);
  2. optionally runs the Terminal’s SQL;
  3. starts the named workflow, bound to that response (EntityType = "WizardFormResponse", with EntityTable recording which wizard’s table the row is in), passing the wizard’s variables as the initial workflow context.

From there the workflow handles approvals, decisions and downstream actions independently. Editing and resubmitting a response can instead continue the existing workflow instance via a resubmission transition — see Runtime & designer.

This is the shape of a wizard end to end — a Startup global, one Input step, an Action that creates a draft row, and a Terminal that starts an approval workflow:

┌───────────────────────────────┐
│ Startup · Channel global │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ Input · Product Details │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ Action · Create Draft (SQL) │
└───────────────┬───────────────┘
▼
┌───────────────────────────────┐
│ Terminal · Submitted │
└───────────────┬───────────────┘
▼ starts on submit
┌───────────────────────────────┐
│ Workflow · ProductApproval │
└───────────────────────────────┘
Prefer a picture?
<Wizard Name="NewProductRequest" Label="New Product Request" Slug="new-product-request" Schema="Inventory">
<!-- 1. Startup: seed a global, then auto-advance -->
<Node Id="start" Type="Startup" Label="Start" X="-60" Y="38" Width="160" Height="60">
<Variables>
<Variable Name="Channel" Type="String" Default="Wizard" />
</Variables>
</Node>
<!-- 2. Input: collect fields (one is a SQL-backed Select) -->
<Node Id="details" Type="Input" Label="Product Details" X="220" Y="-12" Width="200" Height="160">
<Fields>
<Field Name="ProductName" Type="String" Label="Product Name" Required="true" />
<Field Name="CategoryId" Type="Select" Label="Category" Required="true" ControlType="Select2">
<OptionsSql>SELECT Id AS Value, Name AS Label FROM Inventory.Categories WHERE IsActive = 1</OptionsSql>
</Field>
<Field Name="UnitPrice" Type="Number" Label="Unit Price" Required="true" />
</Fields>
</Node>
<!-- 3. Action: run SQL to create a draft, capture the new id into a variable -->
<Node Id="register" Type="Action" Label="Create Draft" X="480" Y="30" ActionType="SetVariable">
<TargetVariable>ProductId</TargetVariable>
<ValueSource>SQL</ValueSource>
<SQL>INSERT INTO Inventory.Products (Name, CategoryId, UnitPrice, Status) VALUES (@ProductName, @CategoryId, @UnitPrice, 'Draft'); SELECT CAST(SCOPE_IDENTITY() AS BIGINT);</SQL>
</Node>
<!-- 4. Terminal: finish and start the ProductApproval workflow -->
<Node Id="done" Type="Terminal" Label="Submitted" X="740" Y="38"
SubmissionStatus="Pending" WorkflowName="ProductApproval" ResubmissionTransition="Resubmitted">
<SuccessMessage>Your product request has been submitted for approval.</SuccessMessage>
</Node>
<Link From="start" To="details" />
<Link From="details" To="register" />
<Link From="register" To="done" />
</Wizard>

The full reference (every node type, field type, SQL block and attribute) is in Authoring a wizard. How it executes and how the designer/runner work is in Runtime & designer.