UNS (Unified Namespace) Nodes
Overview
The Unified Namespace (UNS) is MaestroHub's central data fabric that organizes all operational data into a hierarchical topic tree following the ISA-95 model (enterprise / site / area / line / cell). UNS nodes allow pipelines to interact with this namespace — publishing live data, retrieving historical records, discovering topics, and triggering workflows when new data arrives.
Unlike industrial or database connector nodes, UNS nodes do not require an external connection profile. They communicate directly with the built-in UNS Manager service, making them zero-configuration from a connectivity standpoint.
Topic Structure
Every UNS topic path is prefixed with a version string that scopes the namespace schema. The current default version is mHv1.0.
mHv1.0/enterprise/site/area/machine/temperature
└─────┘ └──────────────────────────────────────┘
version topic path
The system automatically prepends the version prefix if it is not already present in the configured topic. In the UI, a version selector dropdown lets you choose the active version while you type the topic path.
Available Nodes
| Node | Type ID | Category | Purpose |
|---|---|---|---|
| UNS Subscribe Trigger | trigger.uns.subscribe | Trigger | Starts a pipeline when data is published to matching UNS topics |
| UNS Publish | system.uns.publish | UNS | Publishes data to a UNS topic |
| UNS Publish (Batch) | system.uns.publishBatch | UNS | Publishes an array of records to UNS topics atomically in one storage commit |
| UNS Fetch Data | system.uns.fetchdata | UNS | Retrieves the most recent records, or the records in a time range, from a UNS topic |
| UNS Search Nodes | system.uns.searchnodes | UNS | Searches the namespace for topics by keyword |
UNS Topic Selector
The UNS Publish, UNS Publish (Batch), and UNS Fetch Data nodes share a specialized UNS Topic Selector control for configuring the target topic. Understanding this component helps you work with any UNS node that requires a topic.
How It Works
- Version Selector — a dropdown on the left side of the field displays the available namespace versions (fetched from the backend). The default version (
mHv1.0) is pre-selected. - Topic Path Input — a text input where you type the topic path. As you type (minimum 2 characters), the system searches existing topics and shows autocomplete suggestions in a dropdown.
- Expression Support — the field accepts pipeline expressions (
{{ }}syntax). When an expression is detected, autocomplete is disabled and a live preview evaluates the expression against available upstream data. - Full Topic Preview — below the input, a preview badge shows the complete resolved path including the version prefix (e.g.,
mHv1.0/enterprise/site/area/machine/temperature).
Expression Preview States
| State | Indicator | Meaning |
|---|---|---|
| Evaluating | Spinner | Expression is being evaluated. |
| Success | Green checkmark | Expression resolved successfully; shows the resolved value. |
| Missing data | Info icon (muted) | Upstream node data not yet available — run upstream nodes first. |
| Error | Red alert | Syntax error or evaluation failure in the expression. |
UNS Publish
UNS Publish Node
Overview
The UNS Publish node writes data to a UNS topic. It is the primary way pipelines push processed results, enriched telemetry, calculated KPIs, or control signals back into the Unified Namespace for other systems and pipelines to consume.
Publishing is synchronous — the node waits for confirmation from the UNS Manager before completing, ensuring reliable delivery.
Node Handles
| Handle | Position | Description |
|---|---|---|
Input (in) | Left | Receives data from the upstream node. |
Output (out) | Right | Sends the publish confirmation and metadata to downstream nodes. |
Parameters
| Field | UI Control | Data Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|---|
| UNS Topic | UNS Topic Selector (version dropdown + path input with autocomplete) | string | Yes | "" | Must not be empty after expression resolution | Full UNS topic path. The version prefix is auto-prepended if missing. Supports expressions. |
| Value | Expression Field (multiline textarea with preview) | any | Yes | "" | Must not be empty or null | Data to publish. Accepts literal values, JSON objects, or expressions that resolve to any type. |
| Data Source | Expression Field (single-line input with preview) | string | No | "" (auto-generated) | — | Identifier for the data source. If left empty, defaults to pipeline:{pipelineName}:{pipelineId}. Supports expressions. |
| Timestamp | Expression Field (single-line input with preview) | string | No | "" (inherited) | RFC 3339 after expression resolution | When the value was produced. Leave empty to inherit the trigger's device time ($trigger._metadata.sourceTimestamp, else $trigger._metadata.timestamp); a trigger with neither stores the arrival time. This is the time the historian stores and the schema's staleness threshold ages. |
| Quality | Expression Field (single-line input with preview) | string | No | "" (inherited) | good, uncertain, bad (any case) or 0, 1, 2 after expression resolution | The value's quality, e.g. {{ $node["Read"].result[0].quality }} from an OPC UA read group. Combined worst-of with the trigger's own verdict ($trigger._metadata.quality): it can lower trust, never raise it. Leave empty to inherit the trigger's verdict alone. |
The message's quality is always inherited: every message carries a verdict in _metadata.quality — the trigger's on a connector or UNS-subscribe trigger whose reading was uncertain or bad, carried by the engine through every node that makes no statement of its own, folded worst-of by Buffer, Aggregator and Merge, and settable by a JavaScript node returning the envelope. The publish node reads the message's verdict first and the trigger's only when the message states none, so a buffer of 500 rows publishes the verdict of its worst row and not of the firing that flushed it. The Quality parameter adds the pipeline's own statement on top; a pipeline cannot launder a bad reading into a good one.
Settings
Description
A free-text area for documenting the node's purpose.
Execution Settings
| Setting | Options | Default | Description |
|---|---|---|---|
| Timeout (seconds) | 1–600 | Pipeline default | Maximum execution time for this node. |
| Retry on Timeout | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry if the node times out. |
| Retry on Fail | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry on failure. When Enabled, reveals Advanced Retry Configuration. |
| On Error | Pipeline Default / Stop Pipeline / Continue Execution | Pipeline Default | Behavior when the node fails after all retries. |
Advanced Retry Configuration (visible when Retry on Fail = Enabled)
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
| Max Attempts | number | 3 | 1–10 | Maximum retry attempts. |
| Initial Delay (ms) | number | 1000 | 100–30,000 | Wait before first retry. |
| Max Delay (ms) | number | 120000 | 1,000–300,000 | Upper bound for backoff delay. |
| Multiplier | number | 2.0 | 1.0–5.0 | Exponential backoff multiplier. |
| Jitter Factor | number | 0.1 | 0–0.5 | Random jitter. |
Output Data Structure
On successful publish, the node produces:
{
"published": true,
"topic": "mHv1.0/enterprise/site/area/machine/temperature",
"value": { "temperature": 72.5, "unit": "F" },
"source": "pipeline:Temp-Monitor:abc-123",
"timestamp": "2026-02-11T14:15:00Z"
}
| Field | Type | Description |
|---|---|---|
published | boolean | Always true on success. |
topic | string | The full topic path the data was published to (including version prefix). |
value | any | The value that was published, as resolved from the input expression. |
source | string | The source identifier used for this publish operation. |
timestamp | string | ISO 8601 timestamp of when the publish occurred (UTC). |
quality | string | The quality the value was published with: good, uncertain or bad — the trigger's verdict lowered by the Quality parameter, if any. |
qualityReason | string | Only when the trigger gave a reason for its verdict and the node did not lower it: the reason forwarded to the UNS, e.g. comm_error from a connector's read-failure event or range_violation from a UNS-subscribe hop. The stored record reads back as bad · comm_error. |
Referencing in Downstream Nodes
{{ $node["UNS Publish"].result }} → true
{{ $node["UNS Publish"]._metadata.topic }} → the full topic path
{{ $node["UNS Publish"]._metadata.timestamp }} → when it was published
Validation Rules
| Rule | Error Message |
|---|---|
| Node name is required | "UNS Publish node must have a name" |
| UNS topic is required | "UNS topic is required" |
| Value is required | "Value is required" |
| Topic must resolve to non-empty string | "topic resolved to empty string" |
Empty values are published. Whatever the Value resolves to is published as it is, an empty value included: an empty string (a cleared fault code), an empty list (a query that returned no rows), an empty object, and null when the expression names a field the input does not have. The node completes, and inside a For-Each the next item runs as usual. To skip a publish when the value is empty, put an If node in front of it with a condition such as {{ !isEmpty($node["Query"].result) }}.
Usage Examples
Publish Transformed Sensor Data
| Field | Value |
|---|---|
| UNS Topic | mHv1.0/acme-corp/plant-1/assembly/robot-arm/temperature |
| Value | {{ $node["Set"].result.data }} |
| Data Source | (empty — auto-generated) |
The pipeline reads a raw sensor value, transforms it with a Set node, and publishes the enriched result back to the UNS.
Publish Aggregated KPIs
| Field | Value |
|---|---|
| UNS Topic | mHv1.0/acme-corp/plant-1/kpis/oee |
| Value | {"oee": {{ $node["Calculate OEE"].result }}, "shift": "morning", "timestamp": "{{ $execution.startedAt }}"} |
| Data Source | kpi-calculator |
Publishes a calculated OEE metric with context about the shift and execution time.
Dynamic Topic from Trigger Data
| Field | Value |
|---|---|
| UNS Topic | {{ $trigger._metadata.topic }}/processed |
| Value | {{ $node["Transform"].result }} |
| Data Source | (empty) |
Appends /processed to the original trigger topic, creating a parallel processed-data topic hierarchy.
UNS Publish (Batch)
UNS Publish (Batch) Node
Overview
The UNS Publish (Batch) node writes an array of records to the Unified Namespace in a single atomic storage commit. It is the high-volume counterpart to UNS Publish: instead of one topic and one value, you provide an Items expression that resolves to an array, plus per-item templates for the topic and value that are re-evaluated once for each element — exactly like the body of a For-Each loop, but collapsed into one node.
Use it instead of a For-Each + UNS Publish pair whenever you publish many records at once. A For-Each that publishes per item issues one synchronous write-ahead-log fsync per record; the batch node collapses all N writes into one commit. On large batches (thousands of records) this is typically two orders of magnitude faster.
The batch is all-or-nothing: if any record fails template evaluation, resolves to an empty topic, or is denied by authorization, the whole batch aborts before anything is written — you never get a partially-published namespace.
Node Handles
| Handle | Position | Description |
|---|---|---|
Input (in) | Left | Receives data from the upstream node (often the array to publish). |
Output (out) | Right | Sends the batch commit summary and metadata to downstream nodes. |
Evaluation Scope
The configuration form is organized into three sections by when each field is evaluated. Getting this right is the key to using the node correctly:
| Section | Fields | Evaluated | $item in scope? |
|---|---|---|---|
| 1 · Input | Items | Once, at node entry | No — reference the upstream node by name, e.g. $node["Sample Readings"].result |
| 2 · Per-item templates | UNS Topic, Value | Once per element in the resolved Items array | Yes — reference $item, $index, $key |
| 3 · Batch metadata | Data Source | Once, for the whole batch | No |
When the Items expression resolves, the editor automatically takes row [0] as a sample so the Topic and Value fields can show live per-item previews — there is no separate "sample item" field to fill in.
$inputResolve the array by referencing the upstream node by name — {{ $node["Sample Readings"].result }} when a manual trigger carries the array, or {{ $node["Read Group"].result }} from a transform/read node. Avoid {{ $input }}: it is the raw array of upstream outputs, not your records array, so it won't iterate the way you expect.
Parameters
| Field | UI Control | Data Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|---|
| Items | Expression Field (multiline textarea with preview) | array | Yes | {{ $input }} | Must resolve to an array | Expression resolving to the array of records to publish. Each element becomes $item in the per-item templates. Evaluated once. |
| UNS Topic | UNS Topic Selector (version dropdown + path input) | string | Yes | "" | Must not resolve to empty per item | Per-item topic template, re-evaluated for each element — e.g. enterprise/site/{{ $item.name }}. The version prefix is auto-prepended if missing. |
| Value | Expression Field (multiline textarea with preview) | any | Yes | {{ $item }} | Must not be empty | Per-item value template. {{ $item }} publishes the whole row; {{ $item.payload }} picks a field. |
| Data Source | Expression Field (single-line input with preview) | string | No | "" (auto-generated) | — | Source attribution stamped on every record. Evaluated once at batch scope ($item not in scope). Defaults to pipeline:{pipelineName}:{pipelineId} if empty. |
| Timestamp | Expression Field (single-line input with preview) | string | No | "" (inherited) | RFC 3339 per item | When each record was produced, evaluated per item ($item in scope), e.g. {{ $item.ts }}. Leave empty to give every record the trigger's device time, spaced one microsecond apart so items on one topic never share a timestamp; a trigger with neither sourceTimestamp nor timestamp stores arrival time, spaced the same way. One malformed item fails the whole batch. |
| Quality | Expression Field (single-line input with preview) | string | No | "" (inherited) | good/uncertain/bad or 0/1/2 per item | Each record's quality, evaluated per item, e.g. {{ $item.quality }}. Combined worst-of with the trigger's verdict: it can lower a record's trust, never raise it. One malformed item fails the whole batch. |
Unlike the topic and value, Data Source is evaluated once for the entire batch and shared by every record — $item is not available here. If you need a per-record source you must split the batch.
Settings
Description
A free-text area for documenting the node's purpose.
Execution Settings
| Setting | Options | Default | Description |
|---|---|---|---|
| Timeout (seconds) | 1–600 | Pipeline default | Maximum execution time for this node. |
| Retry on Timeout | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry if the node times out. |
| Retry on Fail | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry on failure. When Enabled, reveals Advanced Retry Configuration. |
| On Error | Pipeline Default / Stop Pipeline / Continue Execution | Pipeline Default | Behavior when the node fails after all retries. |
Advanced Retry Configuration (visible when Retry on Fail = Enabled)
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
| Max Attempts | number | 3 | 1–10 | Maximum retry attempts. |
| Initial Delay (ms) | number | 1000 | 100–30,000 | Wait before first retry. |
| Max Delay (ms) | number | 120000 | 1,000–300,000 | Upper bound for backoff delay. |
| Multiplier | number | 2.0 | 1.0–5.0 | Exponential backoff multiplier. |
| Jitter Factor | number | 0.1 | 0–0.5 | Random jitter. |
Because the batch commits atomically, a retry re-publishes the entire array, not just the failed records. Make downstream consumers tolerant of a record being delivered more than once, or keep batches idempotent.
Output Data Structure
On a successful commit, the node produces a summary of the batch (not the individual records):
{
"published": true,
"count": 3,
"distinctTopics": [
"mHv1.0/acme-corp/plant-1/press-01/temperature",
"mHv1.0/acme-corp/plant-1/press-01/pressure",
"mHv1.0/acme-corp/plant-1/press-02/temperature"
],
"source": "pipeline:Line-Ingest:abc-123",
"timestamp": "2026-06-11T14:15:00Z"
}
| Field | Type | Description |
|---|---|---|
published | boolean | Always true on success. |
count | number | Number of rows the store holds for the batch: distinct (topic, timestamp) pairs. The store keeps one record per topic and timestamp, so two items that resolve to the same topic and the same timestamp are one row, the later item. |
duplicates | number | Only present when at least one item collapsed that way: how many items repeated an earlier item's (topic, timestamp) and were overwritten. count + duplicates is the number of items sent. Fix the per-item Timestamp template so every item on a topic carries its own time. |
distinctTopics | array | Deduped set of topics the batch wrote to. Publishing 5,000 records across 10 topics yields 10 entries here, not 5,000. |
source | string | The source attribution recorded on every record. |
timestamp | string | ISO 8601 timestamp of when the batch was committed (UTC). |
If the Items expression resolves to an empty array, the node completes successfully with published: true and count: 0 — nothing is written and the pipeline continues.
Referencing in Downstream Nodes
{{ $node["UNS Publish (Batch)"]._metadata.count }} → number of records published
{{ $node["UNS Publish (Batch)"]._metadata.distinctTopics }} → the deduped topic set written to
{{ $node["UNS Publish (Batch)"]._metadata.timestamp }} → when the batch committed
Validation Rules
| Rule | Error Message |
|---|---|
| Node name is required | "UNS Publish (Batch) node must have a name" |
| Items expression is required | "Items expression is required" |
| Items must resolve to an array | "items must be an array or expression, got …" |
| UNS topic is required | "UNS topic is required" |
| Value is required | "Value is required" |
| A record's topic must resolve to non-empty | "record[N]: topic resolved to empty string" |
| A record may not be nil | "record[N]: item is nil" |
Usage Examples
Publish a Batch of Machine Readings
A Manual Trigger named Sample Readings provides an array of readings as its payload, each with its own machine and metric:
[
{ "machine": "press-01", "metric": "temperature", "value": 72.4 },
{ "machine": "press-01", "metric": "pressure", "value": 5.1 },
{ "machine": "press-02", "metric": "temperature", "value": 68.9 }
]
| Field | Value |
|---|---|
| Items | {{ $node["Sample Readings"].result }} |
| UNS Topic | acme-corp/plant-1/{{ $item.machine }}/{{ $item.metric }} |
| Value | {{ $item.value }} |
| Data Source | (empty — auto-generated) |
Each reading is written to its own topic — mHv1.0/acme-corp/plant-1/press-01/temperature, .../press-01/pressure, .../press-02/temperature — in a single commit. The output reports count: 3 and three distinctTopics.
This is exactly the wiring used by the bundled Demo_UNSPublishBatch pipeline. Open it, run the Sample Readings trigger, and inspect the node output to see count and distinctTopics for the batch.
Replace a For-Each + UNS Publish Loop
If you have a For-Each loop whose body is a single UNS Publish, collapse it into one batch node. The Items expression stays the same as the loop's Source Array; the loop body's Topic and Value templates move onto the batch node unchanged — they already reference $item.
| For-Each + Publish | UNS Publish (Batch) |
|---|---|
Source Array: {{ $node["Read Group"].result }} | Items: {{ $node["Read Group"].result }} |
Publish Topic: plant/{{ $item.tag }} | UNS Topic: plant/{{ $item.tag }} |
Publish Value: {{ $item.value }} | Value: {{ $item.value }} |
The editor surfaces a hint inside the For-Each node when it detects this pattern. Keep the For-Each only if the loop body does extra per-item work the batch node can't express.
Publish Whole Rows Under a Static Topic
When every record goes to the same topic and you want to store the entire row:
| Field | Value |
|---|---|
| Items | {{ $node["Query"].result.rows }} |
| UNS Topic | acme-corp/plant-1/audit/events |
| Value | {{ $item }} |
| Data Source | audit-importer |
Because the topic is a literal string (no $item reference), every record lands on the same topic and distinctTopics contains a single entry.
UNS Fetch Data
UNS Fetch Data Node
Overview
The UNS Fetch Data node retrieves data records stored in the UNS for a given topic: either the most recent ones (Fetch Mode = Recent) or the ones stored between a start and an end time (Fetch Mode = Range). It is ideal for building dashboards, performing trend analysis, or feeding historical context into decision-making logic within a pipeline. Every record carries the verdict the historian stored for it (quality, plus qualityReason when known), and the Quality setting lets the node ask for only the records that pass a bound — good only, or everything but bad — the same filter the Data Explorer's window section offers on its chart and records table.
Node Handles
| Handle | Position | Description |
|---|---|---|
Input (in) | Left | Receives data from the upstream node. |
Output (out) | Right | Sends the fetched records and metadata to downstream nodes. |
Parameters
| Field | UI Control | Data Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|---|
| UNS Topic | UNS Topic Selector (version dropdown + path input with autocomplete) | string | Yes | "" | Must not be empty after expression resolution | The topic to fetch data from. Supports expressions. |
| Fetch Mode | Select | string | No | Recent | Recent / Range | Recent returns the newest records of the topic. Range returns the records stored between Start Time and End Time. |
| Limit | Number input | number | No | 10 | 1–1,000 | Recent mode only. Number of most recent records to retrieve. |
| Start Time | Expression input | string | In Range mode | "" | RFC 3339 timestamp, or an expression that resolves to one | Range mode only. Start of the time window, e.g. 2026-01-01T00:00:00Z or {{ dateAdd(now(), -1, "hour") }}. |
| End Time | Expression input | string | In Range mode | "" | RFC 3339 timestamp, or an expression that resolves to one; must be after Start Time | Range mode only. End of the time window, e.g. {{ now() }}. |
| Range Limit | Number input | number | No | 100 | 1–10,000 | Range mode only. Maximum number of records to return from the window. |
| Quality | Select | string | No | All records | All records / Hide bad records / Good records only | Which stored records to return by their quality verdict. The historian applies the bound before the limit, so hiding bad records never shortens the result — three good records are three good records, not three rows with the bad ones blanked. |
Settings
Description
A free-text area for documenting the node's purpose.
Execution Settings
| Setting | Options | Default | Description |
|---|---|---|---|
| Timeout (seconds) | 1–600 | Pipeline default | Maximum execution time for this node. |
| Retry on Timeout | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry if the node times out. |
| Retry on Fail | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry on failure. When Enabled, reveals Advanced Retry Configuration. |
| On Error | Pipeline Default / Stop Pipeline / Continue Execution | Pipeline Default | Behavior when the node fails after all retries. |
Advanced Retry Configuration (visible when Retry on Fail = Enabled)
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
| Max Attempts | number | 3 | 1–10 | Maximum retry attempts. |
| Initial Delay (ms) | number | 1000 | 100–30,000 | Wait before first retry. |
| Max Delay (ms) | number | 120000 | 1,000–300,000 | Upper bound for backoff delay. |
| Multiplier | number | 2.0 | 1.0–5.0 | Exponential backoff multiplier. |
| Jitter Factor | number | 0.1 | 0–0.5 | Random jitter. |
Output Data Structure
The fetched records are the node's result; the query facts are in _metadata:
{
"result": [
{
"id": "rec-001",
"topic": "mHv1.0/enterprise/site/area/machine/temperature",
"value": { "temperature": 72.5, "unit": "F" },
"source": "pipeline:Temp-Monitor:abc-123",
"timestamp": "2026-02-11T14:10:00Z",
"retain": false,
"quality": "good"
},
{
"id": "rec-002",
"topic": "mHv1.0/enterprise/site/area/machine/temperature",
"value": { "temperature": 73.1, "unit": "F" },
"source": "pipeline:Temp-Monitor:abc-123",
"timestamp": "2026-02-11T14:05:00Z",
"retain": false
}
],
"_metadata": {
"count": 2,
"topic": "mHv1.0/enterprise/site/area/machine/temperature",
"fetchMode": "recent"
}
}
| Field | Type | Description |
|---|---|---|
result | array | Array of data records matching the query. |
result[].id | string | Unique record identifier. |
result[].topic | string | The topic the record belongs to. |
result[].value | any | The stored data value. |
result[].source | string | The source that published the record. |
result[].timestamp | string | ISO 8601 timestamp of when the record was published. |
result[].retain | boolean | Whether this was a retained message. |
result[].quality | string | The quality the record was stored with: good, uncertain or bad — the same band a trigger's _metadata.quality carries, so you can branch on it or pass it to a publish node. |
result[].qualityReason | string | Why a degraded record is degraded, when the platform knows: range_violation, shape_violation, stale, overflow, out_of_service (the asset was in maintenance or stopped mode), comm_error (the connector could not read the device), unit_mismatch (the device reported the value in a different unit than the schema declares), manual (an operator published it), computed, … — the closed reason taxonomy. Absent on good records, on records whose publisher gave no reason, and on rows stored before the reason was kept. |
result[].qualityLimit | string | low or high when the value sat at a declared bound. Absent otherwise. |
result[].qualitySource | string | Who decided the verdict on a degraded record: validator (schema validation at publish) or publisher (the device or pipeline declared it). Absent on good records. |
result[].ingestedAt | string | When the platform accepted the record (RFC 3339, UTC), beside timestamp (when it was produced). Absent on records stored before arrival time was kept. |
result[].latencyMs | number | ingestedAt minus timestamp in milliseconds, floored at zero — the source-to-ingest lag. A buffered or replayed reading shows a large value; a live one shows the transport's delay. Absent with ingestedAt. |
_metadata.count | number | Number of records returned. |
_metadata.topic | string | The topic that was queried. |
_metadata.fetchMode | string | The fetch mode used: "recent" or "range". |
Referencing in Downstream Nodes
{{ $node["UNS Fetch Data"].result }} → the full records array
{{ $node["UNS Fetch Data"].result[0].value }} → the most recent record's value
{{ $node["UNS Fetch Data"]._metadata.count }} → number of records returned
{{ $node["UNS Fetch Data"]._metadata.fetchMode }} → "recent" or "range"
Validation Rules
| Rule | Error Message |
|---|---|
| Node name is required | "UNS Fetch Data node must have a name" |
| UNS topic is required | "UNS topic is required" |
| Limit must be between 1 and 1,000 | "Limit must be between 1 and 1000" |
| Topic must resolve to non-empty string | "topic resolved to empty string" |
| Start Time and End Time are required in Range mode | "start is required when fetchMode is "range" …" (or "end is required …") |
| End Time must be after Start Time | "end (…) must be after start (…) — the window would be empty" |
Usage Examples
Fetch Latest 5 Readings
| Field | Value |
|---|---|
| UNS Topic | mHv1.0/acme-corp/plant-1/assembly/robot-arm/temperature |
| Limit | 5 |
Downstream usage: {{ $node["UNS Fetch Data"].result }} to iterate over the readings in a For Each loop, or {{ $node["UNS Fetch Data"].result[0].value }} for the latest.
Fetch Maximum History for Trend Analysis
| Field | Value |
|---|---|
| UNS Topic | mHv1.0/acme-corp/plant-1/assembly/robot-arm/temperature |
| Limit | 1000 |
Downstream usage: Feed up to 1,000 recent records into a downstream calculation node for trend analysis or anomaly detection.
Fetch the Last Hour
| Field | Value |
|---|---|
| UNS Topic | mHv1.0/acme-corp/plant-1/assembly/robot-arm/temperature |
| Fetch Mode | Range |
| Start Time | {{ dateAdd(now(), -1, "hour") }} |
| End Time | {{ now() }} |
| Range Limit | 1000 |
Downstream usage: {{ $node["UNS Fetch Data"].result }} holds up to 1,000 records stored in the past hour.
Dynamic Topic from Upstream Node
| Field | Value |
|---|---|
| UNS Topic | {{ $node["Set"].result.topic }} |
| Limit | 10 |
Downstream usage: The topic is dynamically determined by an upstream Set node, enabling reusable fetch logic across different data sources.
UNS Search Nodes
UNS Search Nodes
Overview
The UNS Search Nodes node discovers topics in the Unified Namespace by keyword. It returns matching topic metadata with pagination support, making it useful for dynamic topic discovery, namespace exploration, and building guided selection flows.
Node Handles
| Handle | Position | Description |
|---|---|---|
Input (in) | Left | Receives data from the upstream node. |
Output (out) | Right | Sends the search results to downstream nodes. |
Parameters
| Field | UI Control | Data Type | Required | Default | Validation | Description |
|---|---|---|---|---|---|---|
| Search Keyword | Expression Field (single-line input with preview) | string | Yes | "" | Minimum 3 characters (unless it's an expression). Must not resolve to empty. | Keyword to search for in UNS topic names. Supports expressions. |
| Offset | Number input | number | No | 0 | Must be ≥ 0 | Number of results to skip for pagination. |
| Limit | Number input | number | No | 10 | 1–1,000 (clamped to 1,000 if exceeded) | Maximum number of results to return. |
Settings
Execution Settings
| Setting | Options | Default | Description |
|---|---|---|---|
| Timeout (seconds) | 1–600 | Pipeline default | Maximum execution time for this node. |
| Retry on Timeout | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry if the node times out. |
| Retry on Fail | Pipeline Default / Enabled / Disabled | Pipeline Default | Whether to retry on failure. When Enabled, reveals Advanced Retry Configuration. |
| On Error | Pipeline Default / Stop Pipeline / Continue Execution | Pipeline Default | Behavior when the node fails after all retries. |
Advanced Retry Configuration (visible when Retry on Fail = Enabled)
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
| Max Attempts | number | 3 | 1–10 | Maximum retry attempts. |
| Initial Delay (ms) | number | 1000 | 100–30,000 | Wait before first retry. |
| Max Delay (ms) | number | 120000 | 1,000–300,000 | Upper bound for backoff delay. |
| Multiplier | number | 2.0 | 1.0–5.0 | Exponential backoff multiplier. |
| Jitter Factor | number | 0.1 | 0–0.5 | Random jitter. |
Output Data Structure
{
"topics": [
{
"id": "topic-abc-123",
"name": "temperature",
"parentId": "node-xyz-789",
"parentName": "robot-arm",
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-02-11T14:10:00Z"
}
],
"total": 42,
"offset": 0,
"limit": 10,
"keyword": "temperature"
}
| Field | Type | Description |
|---|---|---|
topics | array | Array of matching topic metadata objects. |
topics[].id | string | Unique topic node identifier. |
topics[].name | string | Topic node name (the leaf segment of the topic path). |
topics[].parentId | string | ID of the parent node in the namespace tree. |
topics[].parentName | string | Name of the parent node. |
topics[].createdAt | string | ISO 8601 timestamp of when the topic was created. |
topics[].updatedAt | string | ISO 8601 timestamp of the last update. |
total | number | Total number of matching results (across all pages). |
offset | number | The offset used in this query. |
limit | number | The limit used in this query. |
keyword | string | The keyword that was searched. |
Referencing in Downstream Nodes
{{ $node["UNS Search Nodes"].result }} → the full topics array
{{ $node["UNS Search Nodes"].result[0].name }} → name of the first match
{{ $node["UNS Search Nodes"]._metadata.total }} → total matches found
{{ $node["UNS Search Nodes"]._metadata.keyword }} → the search keyword used
Validation Rules
| Rule | Error Message |
|---|---|
| Node name is required | "UNS Search Nodes node must have a name" |
| Search keyword is required | "Search keyword is required" |
| Keyword must be at least 3 characters (unless expression) | "Search keyword must be at least 3 characters" |
| Offset must be non-negative | "Offset must be a non-negative number" |
| Limit must be between 1 and 1,000 | "Limit must be between 1 and 1000" |
| Keyword must resolve to non-empty string | "keyword resolved to empty string" |
Usage Examples
Discover All Temperature Topics
| Field | Value |
|---|---|
| Search Keyword | temperature |
| Offset | 0 |
| Limit | 50 |
Downstream usage: {{ $node["UNS Search Nodes"].result }} in a For Each loop to process each discovered temperature topic.
Paginated Search with Dynamic Keyword
| Field | Value |
|---|---|
| Search Keyword | {{ $trigger.result.searchTerm }} |
| Offset | {{ $trigger.result.page * 20 }} |
| Limit | 20 |
Downstream usage: Build a searchable topic browser where the keyword and page come from a webhook trigger or manual input.
Namespace Audit
| Field | Value |
|---|---|
| Search Keyword | alarm |
| Offset | 0 |
| Limit | 1000 |
Downstream usage: Discover all alarm-related topics in the namespace for an audit report. {{ $node["UNS Search Nodes"]._metadata.total }} shows the full count.
Best Practices
Topic Naming Conventions
- Follow the ISA-95 hierarchy:
enterprise/site/area/line/cell/resource - Use lowercase, hyphen-separated segments:
acme-corp/plant-1/assembly/robot-arm - Place measured values as leaf nodes:
.../robot-arm/temperature,.../robot-arm/status - Keep topic paths stable — changing paths breaks downstream subscribers
Expression-Driven Topics
- Use expressions like
{{ $trigger._metadata.topic }}to build dynamic, reusable pipelines that work across multiple topics - Combine static prefixes with dynamic suffixes:
mHv1.0/acme-corp/{{ $node["Set"].result.area }}/temperature - Always validate that expressions resolve to non-empty strings before publishing
Error Handling for UNS Nodes
- Enable Retry on Fail for publish operations to handle transient UNS Manager unavailability
- Use Continue Execution on error for fetch and search nodes in non-critical data paths
- Use Stop Pipeline on error for publish nodes in critical data paths where data loss is unacceptable
Performance Considerations
- Keep fetch Limit values as low as practical — fetching 1,000 records when you need 10 wastes resources
- When using UNS Search in a loop, add pagination to avoid fetching all results at once
Configuration reference
The fields below are generated from the node's config contract, so they match what the pipeline validator enforces and what the designer's form offers.
system.uns.fetchdata
| Field | Type | Required | Default | Values | Description |
|---|---|---|---|---|---|
topic | string | yes | — | accepts an expression | UNS topic path to read, e.g. plant/line1/temperature, or an expression such as {{ $trigger._metadata.topic }}; the current version prefix (mHv1.0/) is added when missing |
fetchMode | string | no | recent | recent, range | recent returns the newest records of the topic (up to limit); range returns the records stored between start and end (up to rangeLimit) |
limit | integer | no | 10 | 1–1000 | recent mode: how many of the newest records to return, 1 to 1000 |
start | string | no | — | accepts an expression | range mode: start of the time window as an RFC3339 timestamp, e.g. 2026-01-01T00:00:00Z, or an expression that resolves to one; required when fetchMode is range |
end | string | no | — | accepts an expression | range mode: end of the time window as an RFC3339 timestamp or an expression that resolves to one; must be after start; required when fetchMode is range |
rangeLimit | integer | no | 0 | 0–10000 | range mode: maximum number of records to return, up to 10000; 0 leaves the cap to the UNS service |
maxQuality | string | no | bad | good, uncertain, bad | worst quality band to include: good returns only good records, uncertain hides bad records, bad (the default) returns every record; the bound is applied before limit, so limit still counts the records returned |
system.uns.publish
| Field | Type | Required | Default | Values | Description |
|---|---|---|---|---|---|
topic | string | yes | — | accepts an expression | UNS topic path to publish to, e.g. mHv1.0/site/line1/temperature, or an expression such as {{ $trigger._metadata.topic }} that resolves to it. The version prefix mHv1.0/ is prepended when missing; the resolved path must not be empty |
value | any | yes | — | accepts an expression | The data to publish: a literal number, boolean, object or list, or a string expression such as {{ $node["Read"].result.value }} that resolves to any type. Whatever the value resolves to is published, empty values included: 0 and false, an empty string (a cleared fault code), an empty object, an empty list (a query with no rows), and null for an expression that resolves to nothing. Only a value left blank here is refused |
source | string | no | — | accepts an expression | Identifier recorded as the data's provenance source, e.g. plc-line1; supports expressions. Leave empty to record pipeline:<pipeline name>:<pipeline id> |
timestamp | string | no | — | accepts an expression | When the value was produced, as an RFC 3339 time or an expression that resolves to one, e.g. {{ $node["Read"].result.values[0].ts }}. Leave empty to inherit the trigger's device time ($trigger._metadata.sourceTimestamp, else $trigger._metadata.timestamp); a trigger without either stores the arrival time. This is the time the historian stores and the schema's staleness threshold ages |
quality | string | no | — | accepts an expression | The value's quality: good, uncertain or bad (any case), or 0, 1, 2, or an expression that resolves to one, e.g. {{ $node["Read"].result[0].quality }}. Folded worst-of with the trigger's own verdict ($trigger._metadata.quality), so it can lower trust but never raise it; leave empty to inherit the trigger's verdict alone |
system.uns.publishBatch
| Field | Type | Required | Default | Values | Description |
|---|---|---|---|---|---|
items | any | yes | — | accepts an expression | The records to publish: an array, or an expression that resolves to one, e.g. {{ $input }} or $node["Set"].result.rows. Each element is exposed to the topic and value templates as $item (or $<itemName>), with $index and $key alongside, as inside a foreach body. An empty array publishes nothing and succeeds with count 0 |
topic | string | yes | — | accepts an expression | Topic template evaluated once per item, e.g. mHv1.0/plant/{{ $item.line }}/state — may reference $item. A topic without the mHv1.0/ version prefix gets it prepended |
value | any | yes | — | accepts an expression | What to publish for each item: a string template evaluated per item, e.g. {{ $item.payload }}, or any JSON value published as-is. Required |
itemName | string | no | item | — | Name of the per-item variable inside the topic and value templates, without the $ — default item, so the templates read $item |
source | string | no | — | accepts an expression | Provenance recorded on every record in the batch, evaluated once against the outer context ($item is not in scope), e.g. {{ $node["Set"].result.source }}. Empty records pipeline:<pipeline name>:<pipeline id> |
timestamp | string | no | — | accepts an expression | When each record was produced: an RFC 3339 time or a template evaluated per item, e.g. {{ $item.ts }} — may reference $item. Leave empty to give every record the trigger's device time ($trigger._metadata.sourceTimestamp, else $trigger._metadata.timestamp); a trigger without either stores arrival time |
quality | string | no | — | accepts an expression | Each record's quality: good, uncertain or bad (any case), or 0, 1, 2, or a template evaluated per item, e.g. {{ $item.quality }} — may reference $item. Folded worst-of with the trigger's own verdict, so it can lower trust but never raise it; leave empty to give every record the trigger's verdict alone |
system.uns.searchnodes
| Field | Type | Required | Default | Values | Description |
|---|---|---|---|---|---|
keyword | string | yes | — | accepts an expression | Text to look for in UNS topic names (substring match), or an expression such as {{ $trigger.result.term }} or $input[0].keyword that resolves to it at run time. A literal keyword needs at least 3 characters, and an expression must not resolve to an empty string |
offset | integer | no | 0 | at least 0 | Number of matching topics to skip before the first one returned — the pagination cursor. 0 starts at the first match |
limit | integer | no | 10 | 1–1000 | Maximum number of topics returned by one execution (1 to 1000). The full match count is reported separately as _metadata.total |