Authoring a wizard
A wizard is one *.wizard.xml file: a <Wizard> root, a flat list of <Node> elements, and a
flat list of <Link> edges connecting them. Every example below is drawn from the canonical sample,
NewProductRequest.wizard.xml.
The XML is a lossless serialisation of the visual designer model — you can hand-author it, paste it into the Wizard Designer, or round-trip between the two.
Root — <Wizard>
Section titled “Root — <Wizard>”<Wizard Name="NewProductRequest" Label="New Product Request" Slug="new-product-request" Schema="Inventory"> ...</Wizard>| Attribute | Required | Meaning |
|---|---|---|
Name |
yes | Unique PascalCase identifier. The immutable lookup key; resolvable in the API by name. |
Label |
yes | Human-readable display title. |
Slug |
no | URL slug for the runner route /wizard/{slug}. Omitted → derived (kebab-cased) from Name. |
Schema |
no | Database schema context for the wizard’s SQL. |
Nodes — <Node>
Section titled “Nodes — <Node>”Every node shares the same envelope; the Type selects which children and extra attributes apply.
<Node Id="details" Type="Input" Label="Product Details" X="220" Y="-12" Width="200" Height="160"> ...</Node>| Attribute | Meaning |
|---|---|
Id |
Node identifier, unique within the wizard; referenced by <Link>. |
Type |
One of Startup, Input, Decision, Action, Terminal. |
Label |
Display label (step heading in the runner, node caption in the designer). |
X, Y |
Canvas position (designer layout only). |
Width, Height |
Optional canvas size. |
Startup node
Section titled “Startup node”Initialises global variables then auto-advances. Globals are visible to every later step’s SQL and Decision expressions.
<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><Variable> attributes: Name, Type (String | Int | Decimal | Bool | Date |
DateTime), and optional Default.
Input node
Section titled “Input node”Collects user data. Holds a <Fields> block, an optional <Layout>, and an optional
<SubmitSql>.
<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 IsDeleted = 0 AND IsActive = 1 ORDER BY Name </OptionsSql> </Field> <Field Name="UnitPrice" Type="Number" Label="Unit Price" Required="true" /> <Field Name="ReorderLevel" Type="Number" Label="Reorder Level" Default="10" /> <Field Name="Justification" Type="Text" Label="Justification" /> </Fields></Node>Fields — <Field>
Section titled “Fields — <Field>”| Attribute | Meaning |
|---|---|
Name |
Variable name. Must be PascalCase and unique across all Input nodes in the wizard. |
Type |
String | Text | Number | Date | DateTime | Boolean | Select | Attachment. Text renders multi-line. |
Label |
Field label. |
Required |
true to require a value before advancing. |
Disabled / Readonly |
Render the control disabled / read-only. |
Default |
Seeded value if the field is empty when the step is first shown. |
Mask |
Input mask. |
ControlType |
For Select: Select2 (searchable) or ModalSelector. |
MaxSize, MaxFiles, Accept |
For Attachment: byte cap, file count, accepted MIME/extensions. |
Field children:
<Options>— static select options:<Option Value="…" Label="…" />.<OptionsSql>— dynamic select options; the query must returnValueandLabelcolumns.<SuggestionsSql>— autocomplete suggestions for aTextfield (@SearchTextis injected).<ExtraMappings>— copy extra columns from the picked dataset row into other variables:<Map Column="Email" Variable="ContactEmail" />(e.g. autofill from a chosen record).
Layout — <Layout> (optional)
Section titled “Layout — <Layout> (optional)”An Input step may arrange its fields with the same layout grammar as views —
<Section> / <Grid> / <Row> / <Column Span> / <Item Name Width> / <Line>. See
Form layout. Layout is pure presentation: it is stored in the XML and
consumed by the React runner; fields not placed in the layout fall through in declaration order.
Decision node
Section titled “Decision node”Branches on the current variables. A Decision has no children — it routes purely through its
outgoing <Link Expression="…"> edges (see Links).
<Node Id="creditCheck" Type="Decision" Label="Over 50k?" X="480" Y="30" />Action node
Section titled “Action node”A side effect that runs during the auto-advance burst. The ActionType attribute selects the flavour:
Alert — pause and show a message the user must acknowledge before the flow resumes:
<Node Id="notify" Type="Action" Label="Heads up" ActionType="Alert"> <Subject>Note</Subject> <Message>This request will need manager approval.</Message></Node>ExecuteSQL — run a SQL statement for its side effect:
<Node Id="touch" Type="Action" Label="Log" ActionType="ExecuteSQL"> <SQL>UPDATE Inventory.Requests SET TouchedAt = GETDATE() WHERE Id = @RequestId;</SQL></Node>SetVariable — compute a variable from a static value or a SQL scalar. Use the SQL form with a
trailing SELECT <scalar> to capture something (e.g. a new row’s SCOPE_IDENTITY()) into a
variable for later steps:
<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 (Sku, Name, CategoryId, UnitPrice, UnitCost, ReorderLevel, IsActive, Status, IsDeleted, CompanyId, CreatedById, CreatedAt) VALUES (@SkuFormat, @ProductName, @CategoryId, @UnitPrice, @UnitCost, @ReorderLevel, 0, 'Draft', 0, @SessionCompanyId, @SessionUserId, GETDATE()); SELECT CAST(SCOPE_IDENTITY() AS BIGINT); </SQL></Node><ValueSource> is Static (use <StaticValue>) or SQL (use <SQL>, executed as a scalar).
Terminal node
Section titled “Terminal node”Finishes the wizard and, optionally, hands off to a workflow.
<Node Id="done" Type="Terminal" Label="Submitted" X="740" Y="38" Width="160" Height="60" SubmissionStatus="Pending" WorkflowName="ProductApproval" ResubmissionTransition="Resubmitted"> <SuccessMessage>Your product request has been submitted for approval.</SuccessMessage></Node>| Attribute / child | Meaning |
|---|---|
SubmissionStatus |
Status stamped on the saved response (default Submitted). |
WorkflowName |
Workflow definition to start on submit, bound to the response. Omit for no handoff. |
ResubmissionTransition |
Transition label fired when an edited response continues its existing workflow instance (default Resubmitted). |
<TerminalSql> |
SQL to run at submission time (before the workflow starts). |
<SuccessMessage> |
Message shown on the completion screen. |
Links — <Link>
Section titled “Links — <Link>”Directed edges between nodes. The runner follows them from the start node onward.
<Link From="start" To="details" /><Link From="details" To="register" /><Link From="register" To="done" />| Attribute | Meaning |
|---|---|
From, To |
Source and target node Id. |
Expression |
On a Decision node, the branch condition. First true link wins; Expression="else" (or an expression-less link) is the catch-all. |
FromSide, ToSide |
Which node port the edge attaches to (designer routing only). |
SQL substitution
Section titled “SQL substitution”SQL blocks (<OptionsSql>, <SuggestionsSql>, SetVariable/ExecuteSQL <SQL>, <SubmitSql>,
<TerminalSql>) are string-substituted before execution — this is not a parameterised command:
@FieldNameand@NodeId__FieldNameare replaced with the collected value; strings are auto-quoted, booleans become1/0, andNULLis emitted for empty values.- Session parameters are injected:
@SessionUserId,@SessionCompanyId,@SessionCartId. - Dataset/options queries also receive
@SearchTextfor optional filtering. - Any declared field or Startup variable the user didn’t supply is substituted as SQL
NULL, so referencing an optional blank field never raises “Must declare the scalar variable @X”. - T-SQL locals you
DECLAREinside the SQL body are not wizard variables and are left untouched.
Response view
Section titled “Response view”On save, Genie auto-generates a SQL view [Wizard].[vw_{Name}Responses] that flattens each
response’s JSON into one column per Input field (via JSON_VALUE), so you can build a normal
table view over submitted responses. See
Runtime & designer for how and when it is generated.