Step Engine Reference
The step engine powers guided, multi-step workflows in Aptean Mesh. Instead of building a separate page for each interaction, a workflow page hosts a step input component that dynamically renders the current step based on JSON metadata returned by Business Central (BC).
Each step is described by a StepMetadata JSON object. The client deserializes this object, selects the correct handler from its HandlerRegistry, and renders the appropriate input or display UI. When the user completes a step, the client submits the value back to BC, which responds with the next step's metadata.
Type Hierarchy
Step metadata uses a sealed type hierarchy with a type discriminator field. The client uses polymorphic Kotlin serialization to resolve the concrete class.
StepMetadata (sealed interface)
├── InputStepMetadata (sealed interface)
│ ├── BarcodeMetadata ("barcode")
│ ├── TextMetadata ("text")
│ ├── NumericMetadata ("numeric")
│ ├── DateMetadata ("date")
│ ├── ChoiceMetadata ("choice")
│ └── ImageMetadata ("image")
└── DisplayStepMetadata (sealed interface)
├── SummaryMetadata ("summary")
└── StatusInfoMetadata ("statusinfo")
The string in parentheses is the JSON type discriminator value (from @SerialName in the Kotlin source).
Base Types
StepMetadata
The root sealed interface. Every step -- input or display -- carries these properties.
| Property | Type | Default | Description |
|---|---|---|---|
type | string | required | Discriminator that selects the concrete step type. |
id | string | required | Unique identifier for this step within the workflow. |
prompt | string | required | Primary instruction text displayed to the user. |
label | string? | null | Optional secondary label shown above the input area. |
acceptsHardwareScan | boolean | varies | Whether the step responds to hardware barcode scanner events. Defaults differ per step type. |
suggestValues | string[]? | null | Optional list of suggested values shown in an auto-complete overlay. |
selectedValue | string? | null | Currently selected value from the suggest-values list (used for UOM selection on numeric steps). |
InputStepMetadata
Extends StepMetadata with an input value property. All input step types inherit this.
| Property | Type | Default | Description |
|---|---|---|---|
value | string? | null | Pre-populated value to display when the step loads. |
All properties from StepMetadata are also present.
DisplayStepMetadata
Extends StepMetadata for read-only steps that present information without collecting input. No additional base properties beyond those in StepMetadata.
Input Step Types
BarcodeMetadata
Type discriminator: "barcode"
A scan-first input that accepts hardware barcode scans with a manual-entry fallback. This is the most common step type in warehouse workflows.
| Property | Type | Default | Description |
|---|---|---|---|
placeholder | string? | null | Hint text shown in the manual entry field when empty. |
minLength | int? | null | Minimum acceptable length for manually entered values. |
maxLength | int? | null | Maximum acceptable length for manually entered values. |
manualEntryLabel | string | "Manual Entry" | Label on the button that toggles manual text input. |
Inherited from InputStepMetadata: id, prompt, label, acceptsHardwareScan (default true), value, suggestValues.
{
"type": "barcode",
"id": "scanLocation",
"prompt": "Scan location barcode",
"label": "Location",
"acceptsHardwareScan": true,
"placeholder": "Enter location code",
"minLength": 3,
"maxLength": 20,
"manualEntryLabel": "Type Code"
}
TextMetadata
Type discriminator: "text"
A free-text input field. Supports single-line and multi-line modes.
| Property | Type | Default | Description |
|---|---|---|---|
placeholder | string? | null | Hint text shown when the field is empty. |
minLength | int? | null | Minimum character length for validation. |
maxLength | int? | null | Maximum character length for validation. |
multiline | boolean | false | When true, renders a multi-line text area instead of a single-line field. |
Inherited from InputStepMetadata: id, prompt, label, acceptsHardwareScan (default true), value, suggestValues.
{
"type": "text",
"id": "notes",
"prompt": "Add notes for this receipt",
"label": "Notes",
"placeholder": "Optional notes...",
"multiline": true,
"maxLength": 500,
"acceptsHardwareScan": false
}
NumericMetadata
Type discriminator: "numeric"
A numeric input with optional min/max bounds, increment stepping, and unit-of-measure selection.
| Property | Type | Default | Description |
|---|---|---|---|
placeholder | string? | null | Hint text shown when the field is empty. |
minValue | int? | null | Minimum accepted value. Validation rejects values below this. |
maxValue | int? | null | Maximum accepted value. Validation rejects values above this. |
incrementStep | int | 1 | Step size for the increment/decrement buttons. |
unit | string? | null | Unit label displayed next to the value (e.g., "PCS", "KG"). |
Inherited from InputStepMetadata: id, prompt, label, acceptsHardwareScan (default false), value, suggestValues, selectedValue.
NumericMetadata is the only step type that overrides selectedValue from the base interface. When suggestValues contains a list of UOM codes (e.g., ["PCS", "BOX", "PAL"]), selectedValue holds the currently selected UOM. The client renders a UOM selector overlay for this step type.
{
"type": "numeric",
"id": "quantity",
"prompt": "Enter quantity to pick",
"label": "Qty",
"value": "10",
"minValue": 1,
"maxValue": 999,
"incrementStep": 1,
"unit": "PCS",
"suggestValues": ["PCS", "BOX", "PAL"],
"selectedValue": "PCS"
}
DateMetadata
Type discriminator: "date"
A date picker input with optional min/max constraints. Date values are strings in ISO 8601 format (YYYY-MM-DD).
| Property | Type | Default | Description |
|---|---|---|---|
placeholder | string? | null | Hint text shown when no date is selected. |
minDate | string? | null | Earliest selectable date (ISO 8601 string). |
maxDate | string? | null | Latest selectable date (ISO 8601 string). |
Inherited from InputStepMetadata: id, prompt, label, acceptsHardwareScan (default false), value.
{
"type": "date",
"id": "expiryDate",
"prompt": "Enter expiry date",
"label": "Expiry",
"placeholder": "Select date",
"minDate": "2025-01-01",
"maxDate": "2030-12-31"
}
ChoiceMetadata
Type discriminator: "choice"
A list of predefined options. Supports single-select and multi-select modes.
| Property | Type | Default | Description |
|---|---|---|---|
choices | StepChoice[] | [] | The list of selectable options. See StepChoice below. |
multiSelect | boolean | false | When true, the user can select multiple choices. |
Inherited from InputStepMetadata: id, prompt, label, acceptsHardwareScan (default false), value.
{
"type": "choice",
"id": "reason",
"prompt": "Select a reason code",
"label": "Reason",
"choices": [
{ "value": "DAMAGED", "label": "Damaged goods" },
{ "value": "SHORT", "label": "Short shipment" },
{ "value": "WRONG", "label": "Wrong item" },
{ "value": "OTHER", "label": "Other" }
],
"multiSelect": false
}
ImageMetadata
Type discriminator: "image"
Captures a photo using the device camera. The captured image is converted to a Base64 string before submission.
No additional properties beyond those inherited from InputStepMetadata.
Inherited: id, prompt, label, acceptsHardwareScan (default false), value.
{
"type": "image",
"id": "damagePhoto",
"prompt": "Take a photo of the damage",
"label": "Photo"
}
Display Step Types
SummaryMetadata
Type discriminator: "summary"
A read-only summary screen that presents a list of label-value pairs. Typically used as a confirmation step before final submission.
| Property | Type | Default | Description |
|---|---|---|---|
summaryItems | SummaryItem[] | [] | List of items to display. See SummaryItem below. |
Inherited from StepMetadata: id, prompt, label, acceptsHardwareScan (default false).
{
"type": "summary",
"id": "reviewStep",
"prompt": "Review your entries",
"label": "Summary",
"summaryItems": [
{ "label": "Location", "value": "BIN-A-01" },
{ "label": "Item", "value": "ITEM-1234" },
{ "label": "Quantity", "value": "10 PCS" },
{ "label": "Lot No.", "value": "LOT-2025-001" }
]
}
StatusInfoMetadata
Type discriminator: "statusinfo"
A status banner that communicates the result of an operation. Uses tone and icon to convey severity.
| Property | Type | Default | Description |
|---|---|---|---|
tone | Tone | SUCCESS | Visual tone controlling the color scheme. See Tone Enum below. |
icon | string? | null | Material icon name to display. When null, a default icon is derived from the tone value. |
Inherited from StepMetadata: id, prompt, label, acceptsHardwareScan (default false).
{
"type": "statusinfo",
"id": "result",
"prompt": "Pick completed successfully",
"tone": "SUCCESS",
"icon": "check_circle"
}
Supporting Models
StepChoice
Used by ChoiceMetadata to define selectable options.
| Property | Type | Description |
|---|---|---|
value | string | The value submitted to BC when this option is selected. |
label | string | The display text shown to the user. |
{ "value": "DAMAGED", "label": "Damaged goods" }
SummaryItem
Used by SummaryMetadata to define label-value display pairs.
| Property | Type | Description |
|---|---|---|
label | string | The label shown on the left side of the row. |
value | string | The value shown on the right side of the row. |
{ "label": "Quantity", "value": "10 PCS" }
Tone Enum
The Tone enum controls visual styling for StatusInfoMetadata and other components. It maps to color pairs and default icons.
| Value | Description | Default Icon |
|---|---|---|
LOW | Subtle, de-emphasized appearance | info |
MEDIUM | Standard, neutral appearance | info |
HIGH | Primary, emphasized appearance | info |
ERROR | Red tones indicating errors or destructive actions | error |
SUCCESS | Green tones indicating success or completion | check_circle |
WARNING | Yellow/orange tones indicating caution | warning |
INFO | Blue tones for informational content | info |
Client-Side Architecture
Handler Registry
The client uses a HandlerRegistry to dispatch step metadata to the correct handler. Each step type has a corresponding IStepHandler<M> implementation.
HandlerRegistry
├── BarcodeHandler → BarcodeMetadata
├── TextHandler → TextMetadata
├── NumericHandler → NumericMetadata
├── DateHandler → DateMetadata
├── ChoiceHandler → ChoiceMetadata
├── ImageHandler → ImageMetadata
├── SummaryHandler → SummaryMetadata
└── StatusInfoHandler → StatusInfoMetadata
Each handler implements four methods from the IStepHandler<M> interface:
| Method | Purpose |
|---|---|
createInitialState(metadata, initialValue) | Creates the initial StepInputState from metadata and an optional pre-populated value. |
onEvent(event, state, metadata) | Processes user interaction events (ValueChanged, ClearError, ClearValue, Submit) and returns a new state. |
validate(state, metadata) | Returns a validation error message, or null if the current value is valid. |
getSubmitPayload(state, metadata) | Extracts the value to send back to BC on submission. |
StepInputState
The internal state object managed by each handler during a step's lifecycle:
| Property | Type | Default | Description |
|---|---|---|---|
currentValue | string? | null | The user's current input value. |
isManualEntryMode | boolean | false | Whether the barcode step has toggled to manual text input. |
isFocused | boolean | false | Whether the input field currently has keyboard focus. |
isLoading | boolean | false | Whether an asynchronous operation is in progress. |
errorMessage | string? | null | Validation error text displayed below the input. |
Submission Payload
When the user completes a step, the client sends the value back to BC via the action system. The payload structure is:
{
"value": "<submitted value>",
"barcode": {}
}
For barcode steps where the hardware scanner fires, the barcode object contains the parsed barcode result instead of a manual value. For image steps, the value is a Base64-encoded string of the captured photo.
Mapping to AL Concepts
The step engine JSON schema maps directly to objects and enums in the Business Central AL codebase.
StepType and StepInputType Enums
In BC, each workflow step is defined in the MobileWorkflowStep table. The StepType field determines whether the step collects input or displays information, while StepInputType selects the specific input variant.
JSON type | AL StepType | AL StepInputType | Description |
|---|---|---|---|
"barcode" | Input | Barcode | Hardware scan with manual fallback |
"text" | Input | Text | Free-text entry |
"numeric" | Input | Numeric | Number entry with constraints |
"date" | Input | Date | Date picker |
"choice" | Input | Choice | Single or multi-select list |
"image" | Input | Image | Camera capture |
"summary" | Display | -- | Read-only review |
"statusinfo" | Display | -- | Result banner |
MobileWorkflowStep Table
The step metadata JSON is built dynamically by your AL codeunit's IStepHandler implementation (not to be confused with the client-side Kotlin IStepHandler). The AL handler reads step configuration from the MobileWorkflowStep table and serializes it into the JSON format documented above.
Key fields in the table:
| Field | Maps To |
|---|---|
Step ID | id |
Prompt | prompt |
Label | label |
Step Type | Determines InputStepMetadata vs DisplayStepMetadata |
Step Input Type | The type discriminator value |
Min Value / Max Value | minValue, maxValue (numeric) or minDate, maxDate (date) |
UOM Code | unit (numeric) |
Choices | Serialized into choices array (choice) |
IStepHandler (AL Side)
Your AL codeunit implements the step handler interface to build and return step metadata:
// Simplified example -- actual interface varies by workflow
procedure GetCurrentStep(var StepJson: JsonObject)
begin
// Read from MobileWorkflowStep
// Build the JSON metadata object
// Return to the client
end;
procedure ProcessStepResult(StepId: Text; Value: Text; BarcodeData: JsonObject)
begin
// Validate the submitted value
// Advance to the next step or return an error
// Return the next step's metadata
end;
Complete Workflow Example
The following example shows a realistic four-step put-away workflow. Each step is returned one at a time by BC -- the client renders the current step, collects input, submits, and receives the next step in the response.
Step 1 -- Scan the bin barcode
{
"type": "barcode",
"id": "scanBin",
"prompt": "Scan the destination bin",
"label": "Bin",
"acceptsHardwareScan": true,
"placeholder": "Enter bin code",
"minLength": 1,
"maxLength": 20,
"manualEntryLabel": "Type Bin Code"
}
Step 2 -- Enter the quantity
{
"type": "numeric",
"id": "enterQty",
"prompt": "Enter quantity to put away",
"label": "Quantity",
"value": "5",
"minValue": 1,
"maxValue": 100,
"incrementStep": 1,
"unit": "PCS",
"suggestValues": ["PCS", "BOX", "PAL"],
"selectedValue": "PCS"
}
Step 3 -- Select a reason (if partial quantity)
{
"type": "choice",
"id": "reason",
"prompt": "Why is the quantity less than expected?",
"label": "Reason",
"choices": [
{ "value": "DAMAGED", "label": "Damaged on receipt" },
{ "value": "SHORT", "label": "Short delivery" },
{ "value": "MISCOUNT", "label": "Count discrepancy" }
],
"multiSelect": false
}
Step 4 -- Review and confirm
{
"type": "summary",
"id": "review",
"prompt": "Confirm put-away details",
"label": "Review",
"summaryItems": [
{ "label": "Bin", "value": "BIN-A-03-02" },
{ "label": "Item", "value": "WIDGET-500" },
{ "label": "Quantity", "value": "3 PCS" },
{ "label": "Reason", "value": "Short delivery" }
]
}
Result -- Success status
After the user confirms, BC processes the put-away and returns a status step:
{
"type": "statusinfo",
"id": "result",
"prompt": "Put-away completed for BIN-A-03-02",
"tone": "SUCCESS",
"icon": "check_circle"
}