Configuring a Node
Three JSON properties. One config block. Full control over which handler to use, at which scope granularity, with optional handler-specific options passed through.
The dataStateMachine Block
Add a dataStateMachine property to the node's settings JSON in Flow Studio. The platform reads it via BaseNodeExecutorSettings.DataStateMachineConfig using ReadConfigDictionaryByKey("dataStateMachine").
{
"myNodeSetting1": "value",
"myNodeSetting2": 42,
"dataStateMachine": {
"handler": "built-in",
"scope": "Element",
"options": {}
}
}
Properties Reference
| Property | Type | Required | Values | Default |
|---|---|---|---|---|
| handler | string | No | "built-in", "sales-leads", or any registered HandlerKey | "built-in" |
| scope | string | No | "App", "Process", "Thread", "Element", "ProcessExecution", "ThreadExecution", "ElementExecution" | "Element" |
| options | object | No | Handler-specific key-value pairs | {} |
Scope Selection Guide
Choose the scope based on the granularity of idempotency you need. The scope determines which context fields are included in the state record lookup.
| Scope | When to Use | Example |
|---|---|---|
| App | Global deduplication — once per tenant per key, regardless of workflow | Daily digest email — send once per day globally |
| Process | Dedup within a specific workflow definition | Invoice workflow — don't re-process same invoice in this workflow |
| Thread | Dedup within a specific thread | Approval thread — same approver can't act twice in same thread |
| Element | Node-level idempotency (recommended default) | HttpCall node — don't call same endpoint twice for same entity |
| ProcessExecution | Per-run tracking (different runs = different records) | Audit log — record every run separately |
| ThreadExecution | Per-thread-run tracking | Timing metrics per thread run |
| ElementExecution | Finest — separate record per node per run | Debug tracking for every individual execution |
Handler-Specific Options
The options object is passed through to the handler as a dictionary. The handler reads it from ctx.HandlerConfig.Options. The built-in handler currently uses no options, but custom handlers can declare their own.
{
"dataStateMachine": {
"handler": "my-blockchain-handler",
"scope": "Element",
"options": {
"networkId": "ethereum-mainnet",
"contractAddress": "0x...",
"confirmationsRequired": 3
}
}
}
// Inside MyBlockchainHandler
var networkId = ctx.HandlerConfig.Options.GetValueOrDefault("networkId", "testnet");
var confirmations = int.Parse(ctx.HandlerConfig.Options.GetValueOrDefault("confirmationsRequired", "1"));
What Happens When the Block Is Absent
If the dataStateMachine section is missing from the node settings, DataStateMachineFactory returns NullDataStateMachine.Instance. All calls on IDataStateMachine become no-ops. Nothing is written to the database.
// DataStateMachineFactory.CreateAsync (simplified)
if (config == null)
return NullDataStateMachine.Instance;
// resolve handler, build context, return ContextualDataStateMachine...
DataStateMachine.ExistsAsync() and all other methods even when NullDataStateMachine is active. The no-op returns safe defaults — false for exists checks, true for lock acquisition — so the node logic flows naturally without any null checks.
Reading Config in Your Settings Class
Your custom node settings class inherits from BaseNodeExecutorSettings. DataStateMachineConfig is already declared there — you don't need to add it.
// BaseNodeExecutorSettings (in ProcessEngine.Domain — already exists)
public abstract class BaseNodeExecutorSettings
{
// ... other base properties ...
public DataStateMachineConfig? DataStateMachineConfig =>
ReadConfigDictionaryByKey("dataStateMachine") is { } d
? new DataStateMachineConfig(d)
: null;
}
// Your settings class — DataStateMachineConfig inherited automatically
public class InvoiceDispatchSettings : BaseNodeExecutorSettings
{
public string ApiEndpoint { get; set; } = "";
public int RetryCount { get; set; } = 3;
// DataStateMachineConfig is inherited — no action needed
}
DataStateMachineConfig includes a static DataStateMachineConfig.Empty instance for use in tests and NullDataStateMachine scenarios — it has HandlerKey = "built-in", Scope = "Element", and an empty options dictionary.