MQTT Subscription Reference
| Platform: | AvailableWebNot availableMobile |
|---|---|
| Plan Type: | Not availableBasicNot availableEssentialAvailablePremiumAvailableEnterprise |
| User Type: | Not availableRequesterNot availableFull UserAvailableAdministrator |
This reference describes the options available in the MQTT subscriptions that the MaintainX® On-Premise Agent (referred to as the OPA) uses to decide which topics to listen to, how to decode their payloads, and how to name the resulting tags in MaintainX.
It focuses on the Custom subscription type, which is the most flexible and applies to any MQTT broker. For the full list of subscription types, see Subscription Types. To add a subscription to your OPA, see Configure MQTT Subscriptions.
Subscription Types​
The OPA supports the following subscription types:
SubscriptionType | When to Use |
|---|---|
Custom | Any MQTT broker. You control topic patterns, payload decoding, and field mapping. |
Sparkplug | Brokers using the Sparkplug B specification. |
Zigbee2MQTT | Brokers fronting a Zigbee2MQTT bridge. |
The Custom type is the most flexible and applies to any MQTT broker. The rest of this reference describes Custom subscriptions.
Subscription Anatomy​
Each entry in Subscriptions is an object with the following fields:
| Field | Required | Description |
|---|---|---|
Topic | Yes | The MQTT topic pattern. It supports the + wildcard per segment. The # wildcard isn't supported for Custom subscriptions. For details, see Wildcard Rules. |
SubscriptionType | Yes | This field must be set to Custom for the configurations described on this page. |
Payload | Yes (for Custom) | An object that describes how to decode the payload. For details, see Payload Encoding. |
Description | No | A free-text annotation. It has no effect on OPA behavior and is useful for documenting your configuration. |
TimestampField | No | The top-level JSON field whose value is used as the reading timestamp. This applies only to Json encoding. |
The top-level MqttSubscriptionSettings block also accepts:
| Field | Required | Description |
|---|---|---|
Version | Yes | The schema version. Set this to "v1". |
Subscriptions | Yes | An array of subscription objects (at least one is required). |
IgnoredTopics | No | An array of topic patterns to exclude from processing. For details, see Ignored Topics. |
Payload Encoding​
The Payload.Encoding field selects one of two decoders:
Encoding | When to Use |
|---|---|
Raw | Each topic carries a single scalar value (a number, a boolean, or a string). |
Json | The topic carries a JSON object from which the OPA extracts one or more fields. |
Raw Encoding​
Use Raw when the entire MQTT payload is the value to record. One subscription with Raw encoding maps to one tag per matched topic.
{
"Topic": "plant/boiler/temperature",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Number",
"Unit": "°C",
"FriendlyName": "Boiler Temperature"
}
}
Raw payload fields:
| Field | Required | Description |
|---|---|---|
Encoding | Yes | The encoding type. It must be set to "Raw". |
Type | Yes | The data type. It must be one of "String", "Number", "Boolean", or "Auto". For details, see Type Auto: Automatic Type Inference. |
FriendlyName | No | The name to use for the tag in MaintainX, subject to the Tag Naming Matrix. |
Unit | No | The unit of measurement (e.g., °C or psi). This applies only when Type resolves to a numeric value. |
Description | No | A free-text description shown on the tag in MaintainX. |
JSON Encoding​
Use Json when the payload is a JSON object and you want to extract one or more fields. Each field becomes a separate tag in MaintainX.
{
"Topic": "plant/line1/+",
"SubscriptionType": "Custom",
"TimestampField": "ts",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "data.temperature",
"Type": "Number",
"FriendlyName": "Temperature",
"Unit": "°C"
},
{
"Path": "data.humidity",
"Type": "Number",
"FriendlyName": "Humidity",
"Unit": "%"
},
{
"Path": "data.online",
"Type": "Auto"
}
]
}
}
Json payload fields:
| Field | Required | Description |
|---|---|---|
Encoding | Yes | The encoding type. It must be set to "Json". |
Fields | Yes | An array of 1 to 1,000 field mappings. Each field mapping becomes a tag. |
Each entry in Fields accepts:
| Field | Required | Description |
|---|---|---|
Path | Yes | The dot-separated path to the value (e.g., data.temperature). Array indexes are supported using square brackets (e.g., data.readings[0].value). |
Type | Yes | The data type. It must be one of "String", "Number", "Boolean", or "Auto". |
FriendlyName | No | The name to use for the tag. It's subject to the Tag Naming Matrix. |
Description | No | A free-text description shown on the tag. This field is mutually exclusive with DescriptionPath. |
DescriptionPath | No | The JSON path used to read the description from each payload. This field is mutually exclusive with Description. |
Unit | No | The static unit of measurement. This applies only to numeric types. This field is mutually exclusive with UnitPath. |
UnitPath | No | The JSON path used to read the unit from each payload. This applies only to numeric types. This field is mutually exclusive with Unit. |
The subscription-level TimestampField names a top-level JSON field whose value will be used as the reading timestamp. The OPA supports both ISO-8601 strings and Unix epoch values (seconds, milliseconds, or microseconds). When TimestampField is set, that field is excluded from tag extraction.
Type Auto: Automatic Type Inference​
Type: "Auto" lets the OPA decide a tag's data type from the payload itself, instead of forcing you to declare it up front. It works for both Raw and Json encodings.
How Inference Works​
When the first matching payload arrives, the OPA inspects the value and picks a type in this order:
Boolean: Values that parse astrue/false(case-insensitive).Number: Any value that parses as a decimal number, including integer-looking strings, such as42.String: Anything else not covered byBooleanorNumbervalues.
All numeric values are stored as double-precision floats. This keeps a tag's storage stable when its values alternate between integers (3) and decimals (3.1), so you don't need to predict the future shape of your data.
Raw + Auto: JSON-Looking Payloads Are Skipped​
Auto is meant for scalar values. If a Raw subscription with Type: "Auto" receives a payload that starts with { or [, the OPA silently skips it and emits a debug log. Use Json encoding when your payload is structured.
Unit Handling Under Auto​
The Unit field is only meaningful for numeric tags. When Auto resolves to a Boolean or String, the configured Unit is suppressed and not stored on the tag. This applies to both static Unit and dynamic UnitPath.
Example: Raw + Auto on a Wildcard Topic​
A factory line publishes one value per topic, where the topic's last segment is the tag's identifier. The OPA should pick the right type for each tag automatically:
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
| Received Topic | Payload | Tag Name | Inferred Type |
|---|---|---|---|
factory/line1/oven_temp | 185.4 | oven_temp | Number |
factory/line1/door_open | true | door_open | Boolean |
factory/line1/state | RUNNING | state | String |
Example: JSON + Auto on a Single Field​
A device reports a single field that may be reported as either a boolean or a numeric value:
{
"Topic": "devices/+/online",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "online",
"Type": "Auto",
"FriendlyName": "Connectivity"
}
]
}
}
The first payload received determines the tag's data type. For details on what happens if later payloads use a different type, see Data Type Locking.
Data Type Locking​
A tag's data type is set on first creation and never updated afterward. This applies to explicit types (Number, String, Boolean) and to Auto, where the first inferred type wins.
If a later payload for an existing tag has a different type than the one stored at creation, the OPA drops the payload and logs a one-time warning per tag:
Sensor 'oven_temp' is stored as Double but received String. Payloads with mismatched types will be dropped. To change the sensor type, delete the sensor in MaintainX and let the agent re-create it.
The warning fires once per tag per OPA lifetime to avoid log spam. You should check logs immediately after a configuration change to ensure that no type mismatches are occurring and payloads aren't being dropped.
Changing a Tag's Type​
Because the data type is locked, restarting the OPA doesn't reset it. The only way to change a tag's type is:
- Delete the tag in MaintainX.
- Wait for the next matching payload. The OPA will recreate the tag with the new type.
Tag Naming Matrix​
The OPA derives each tag's name from a combination of the received topic and the field configuration. The most important variable is the last segment of the actual topic that the broker delivered (referred to below as lastSegment), which isn't necessarily the same as the last segment of the subscription pattern.
For example, with the pattern factory/line1/+:
- A message on
factory/line1/oven_temphaslastSegment = "oven_temp". - A message on
factory/line1/door_openhaslastSegment = "door_open".
A topic pattern is said to have a trailing wildcard when its last segment is +. A + in any other position is a middle wildcard and doesn't affect naming.
Naming for Raw Encoding​
| Topic Pattern | FriendlyName | Resulting Tag Name |
|---|---|---|
| No trailing wildcard | Set | FriendlyName |
| No trailing wildcard | Not set | lastSegment |
Middle + only | Set | FriendlyName |
Middle + only | Not set | lastSegment |
Trailing + | (rejected) | Configuration is rejected at startup. For details, see Wildcard Rules. |
Naming for JSON Encoding​
| Topic Pattern | Number of Fields | FriendlyName | Resulting Tag Name |
|---|---|---|---|
| No trailing wildcard | 1 | Set | FriendlyName |
| No trailing wildcard | 1 | Not set | lastSegment |
| No trailing wildcard | 2 or more | Set | lastSegment/FriendlyName |
| No trailing wildcard | 2 or more | Not set | lastSegment/Path |
Trailing + | 1 | Any | lastSegment (FriendlyName is ignored) |
Trailing + | 2 or more | Set | lastSegment/FriendlyName |
Trailing + | 2 or more | Not set | lastSegment/Path |
The MaintainX tag mapping experience lets you rename tags after the OPA creates them. The matrix only applies to the OPA's initial naming. For more information, see Map Tags to Meters.
Worked Naming Examples​
The examples below show the matrix in practice. Each shows the subscription, a sample received topic, and the resulting tag names.
Example 1: Raw, No Wildcard, With FriendlyName​
{
"Topic": "plant/boiler/temperature",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Number",
"Unit": "°C",
"FriendlyName": "Boiler Temperature"
}
}
| Received Topic | Tag Name |
|---|---|
plant/boiler/temperature | Boiler Temperature |
Example 2: Raw + Auto With Trailing Wildcard​
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
| Received Topic | Tag Name | Inferred Type |
|---|---|---|
factory/line1/oven_temp | oven_temp | Number |
factory/line1/door_open | door_open | Boolean |
FriendlyName can't be set here. For more information, see Wildcard Rules.
Example 3: JSON, Multiple Fields, No Wildcard​
{
"Topic": "plant/line1/tags",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{ "Path": "temperature", "Type": "Number", "FriendlyName": "Temperature", "Unit": "°C" },
{ "Path": "humidity", "Type": "Number", "FriendlyName": "Humidity", "Unit": "%" }
]
}
}
| Received Topic | Tag Names |
|---|---|
plant/line1/tags | tags/Temperature, tags/Humidity |
When more than one field is extracted, the last segment is prefixed to keep tag names unique within the group.
Example 4: JSON, Multiple Fields, Middle Wildcard, No FriendlyName​
{
"Topic": "plant/+/readings",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{ "Path": "temp", "Type": "Number", "Unit": "°C" },
{ "Path": "pressure", "Type": "Number", "Unit": "psi" }
]
}
}
| Received Topic | Tag Names |
|---|---|
plant/line1/readings | readings/temp, readings/pressure |
plant/line2/readings | readings/temp, readings/pressure |
Note that the + here is a middle wildcard, not a trailing one. Without a FriendlyName, each field's Path is used as the suffix.
Wildcard Rules​
The OPA enforces two rules at startup. It'll refuse to start if any subscription violates them.
# Isn't Supported for Custom Subscriptions​
The multi-level # wildcard isn't supported on Custom subscriptions. Use the per-segment + wildcard at each level instead.
| Not Supported | Use Instead |
|---|---|
factory/# | factory/+ (one level), or list each level explicitly |
FriendlyName Is Rejected on Raw Subscriptions Ending With +​
When a Raw subscription's topic ends with +, the wildcard segment is what makes each matched topic distinct. Setting a static FriendlyName would collapse every matched topic into a single tag, resulting in each topic overwriting the data of the others. To prevent this, the OPA rejects the configuration at startup with the following error:
'FriendlyName' can't be set when the topic ends with '+' at /Subscriptions/{N}/Payload (topic: '<your-topic>'). The last topic segment is used as the sensor name to distinguish each matched topic; a static FriendlyName would collapse them all to the same sensor. Remove the 'FriendlyName' field to let the last topic segment be used as the sensor name.
The same restriction doesn't apply to Json encoding, because the field's Path (or FriendlyName) is appended to the last segment when there are multiple fields, keeping tag names unique.
Overlapping Subscriptions​
Avoid configuring subscriptions whose topic patterns can match the same incoming topic, including overlaps between a wildcard pattern and a literal one. For example, factory/+/temp and factory/line1/temp both match an incoming topic of factory/line1/temp.
If more than one subscription matches an incoming topic at runtime, the OPA uses the first matching subscription (in the order listed under Subscriptions) and ignores the others. Duplicate or overlapping subscriptions may generate a warning or validation error, depending on your OPA version.
To avoid ambiguous tag data, keep each topic pattern in Subscriptions distinct enough that no incoming topic can match more than one entry.
Ignored Topics​
Use IgnoredTopics at the top level of MqttSubscriptionSettings to filter out topics that would otherwise match a subscription. Patterns use the same + wildcard rules as subscription topics.
{
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": { "Encoding": "Raw", "Type": "Auto" }
}
],
"IgnoredTopics": [
"factory/line1/heartbeat",
"factory/line1/diagnostics/+"
]
}
}
A topic that matches any entry in IgnoredTopics is dropped before subscription matching runs.