Skip to content

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.

<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.

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.

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.

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>
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 return Value and Label columns.
  • <SuggestionsSql> — autocomplete suggestions for a Text field (@SearchText is 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).

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.

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" />

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).

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.

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 blocks (<OptionsSql>, <SuggestionsSql>, SetVariable/ExecuteSQL <SQL>, <SubmitSql>, <TerminalSql>) are string-substituted before execution — this is not a parameterised command:

  • @FieldName and @NodeId__FieldName are replaced with the collected value; strings are auto-quoted, booleans become 1/0, and NULL is emitted for empty values.
  • Session parameters are injected: @SessionUserId, @SessionCompanyId, @SessionCartId.
  • Dataset/options queries also receive @SearchText for 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 DECLARE inside the SQL body are not wizard variables and are left untouched.

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.