Configure MQTT Subscriptions
| Platform: | AvailableWebNot availableMobile |
|---|---|
| Plan Type: | Not availableBasicNot availableEssentialAvailablePremiumAvailableEnterprise |
| User Type: | Not availableRequesterNot availableFull UserAvailableAdministrator |
This article explains how to configure the MQTT subscriptions used by the MaintainXยฎ On-Premise Agent. Subscriptions define which MQTT topics the agent listens to, how it decodes their payloads, and how it names the resulting sensors in MaintainX.
For installation and the base settings.json skeleton, see Setup and Configure the MQTT On-Premise Agent.
Overviewโ
MQTT subscriptions are declared under the MqttSubscriptionSettings block of settings.json. Each subscription pairs a topic pattern with rules that tell the agent how to turn incoming payloads into sensor readings.
{
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
],
"IgnoredTopics": []
}
}
The agent supports several subscription types. The public types are:
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. |
This page focuses on the Custom type, which is the most flexible and applies to any MQTT broker.
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 is not supported for Custom subscriptions. 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. See Payload Encoding. |
Description | No | A free-text annotation. It has no effect on agent 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. 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 agent 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 1 sensor 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". See Type Auto: Automatic Type Inference. |
FriendlyName | No | The name to use for the sensor in MaintainX. It is subject to the Sensor 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 sensor 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 sensor 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โ10 field mappings. Each field mapping becomes a sensor. |
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 sensor. It is subject to the Sensor Naming Matrix. |
Description | No | A free-text description shown on the sensor. This field is mutually exclusive from DescriptionPath. |
DescriptionPath | No | The JSON path used to read the description from each payload. This field is mutually exclusive from Description. |
Unit | No | The static unit of measurement. This applies only to numeric types. This field is mutually exclusive from 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 from Unit. |
The subscription-level TimestampField names a top-level JSON field whose value will be used as the reading timestamp. The agent supports both ISO-8601 strings and Unix epoch values (seconds, milliseconds, or microseconds). When TimestampField is set, that field is excluded from sensor extraction.
Type Auto: Automatic Type Inferenceโ
Type: "Auto" lets the agent decide a sensor'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 agent inspects the value and picks a type in this order:
- Boolean: Values that parse as
true/false(case-insensitive). - Number: Any value that parses as a decimal number, including integer-looking strings such as
42. - String: Anything else not covered by Boolean or Number values.
All numeric values are stored as double-precision floats. This keeps a sensor'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 agent 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 sensors. When Auto resolves to a Boolean or String, the configured Unit is suppressed and not stored on the sensor. 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 sensor's identifier. The agent should pick the right type for each sensor automatically:
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
| Received Topic | Payload | Sensor 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:
{
"Topic": "devices/+/online",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "online",
"Type": "Auto",
"FriendlyName": "Connectivity"
}
]
}
}
The first payload received determines the sensor's data type. For details on what happens if later payloads use a different type, see Data Type Locking.
Data Type Lockingโ
A sensor'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 sensor has a different type than the one stored at creation, the agent drops the payload and logs a one-time warning per sensor:
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 sensor per agent lifetime to avoid log spam. You should check logs immediately after a configuration change to ensure that no type mismatches are occurring and payloads are not being dropped.
Changing a Sensor's Typeโ
Because the data type is locked, restarting the On-Premise Agent does not reset it. The only way to change a sensor's type is:
- Delete the sensor in MaintainX.
- Wait for the next matching payload โ the agent will recreate the sensor with the new type.
Sensor Naming Matrixโ
The agent derives each sensor'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 is not 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 does not affect naming.
Naming for Raw Encodingโ
| Topic Pattern | FriendlyName | Resulting Sensor 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. See Wildcard Rules. |
Naming for JSON Encodingโ
| Topic Pattern | Number of Fields | FriendlyName | Resulting Sensor 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 sensors after the agent creates them. The matrix only applies to the agent's initial naming. For details, see Smart Tag Mapping for OT Data.
Worked Naming Examplesโ
The examples below show the matrix in practice. Each shows the subscription, a sample received topic, and the resulting sensor name(s).
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 | Sensor 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 | Sensor Name | Inferred Type |
|---|---|---|
factory/line1/oven_temp | oven_temp | Number |
factory/line1/door_open | door_open | Boolean |
FriendlyName cannot be set here. For more information, see Wildcard Rules.
Example 3: JSON, Multiple Fields, No Wildcardโ
{
"Topic": "plant/line1/sensors",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Json",
"Fields": [
{ "Path": "temperature", "Type": "Number", "FriendlyName": "Temperature", "Unit": "ยฐC" },
{ "Path": "humidity", "Type": "Number", "FriendlyName": "Humidity", "Unit": "%" }
]
}
}
| Received Topic | Sensor Names |
|---|---|
plant/line1/sensors | sensors/Temperature, sensors/Humidity |
When more than one field is extracted, the last segment is prefixed to keep sensor names unique within the group.
Example 4: JSON, Multiple Fields, Trailing 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 | Sensor 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 On-Premise Agent enforces two rules at startup. The agent will refuse to start if any subscription violates them.
# Is Not Supported for Custom Subscriptionsโ
The multi-level # wildcard is not 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 sensor, overwriting one another's data. To prevent this, the agent rejects the configuration at startup with the following error:
'FriendlyName' cannot 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 does not apply to Json encoding, because the field's Path (or FriendlyName) is appended to the last segment when there are multiple fields, keeping sensor names unique.
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.
Full settings.json Exampleโ
The following example combines a Raw + Auto subscription on a wildcard topic, a multi-field Json subscription with dynamic units and a timestamp, and an IgnoredTopics entry:
{
"Integration": {
"AccessToken": "{MaintainX-Connector-Token}"
},
"Mqtt": {
"client": {
"credentials": {
"username": "{MQTT-Broker-Username}",
"password": "{MQTT-Broker-Password}"
},
"tcpServer": {
"server": "{MQTT-Broker-Host}",
"port": 8883
},
"tls": {
"useTls": true
}
}
},
"MqttSubscriptionSettings": {
"Version": "v1",
"Subscriptions": [
{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Description": "Per-tag scalar values from line 1",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
},
{
"Topic": "plant/+/readings",
"SubscriptionType": "Custom",
"TimestampField": "ts",
"Payload": {
"Encoding": "Json",
"Fields": [
{
"Path": "temperature",
"Type": "Number",
"FriendlyName": "Temperature",
"UnitPath": "units.temperature"
},
{
"Path": "pressure",
"Type": "Number",
"FriendlyName": "Pressure",
"UnitPath": "units.pressure"
},
{
"Path": "online",
"Type": "Auto",
"FriendlyName": "Online"
}
]
}
}
],
"IgnoredTopics": [
"factory/line1/heartbeat"
]
}
}
Troubleshootingโ
The Agent Fails to Start With a Validation Errorโ
The agent validates MqttSubscriptionSettings at startup. The two most common failures are:
#wildcard in aCustomsubscription- Issue: The
#wildcard is not supported in Custom subscriptions. - Resolution: Replace
#with+per level.
- Issue: The
FriendlyNameset on aRawsubscription whose topic ends with+- Issue: Raw subscriptions with wildcard topics cannot have a
FriendlyName. - Resolution: Remove the
FriendlyName. For more information, see Wildcard Rules.
- Issue: Raw subscriptions with wildcard topics cannot have a
The error message in the agent's logs identifies the offending subscription by its index in the Subscriptions array.
No Sensors Are Being Createdโ
Tail the agent logs:
docker logs -f on-premise-agent
Common causes:
Topic did not match any subscription- Issue: The broker is delivering messages, but no topic pattern matches.
- Resolution: Check for typos and remember that
#is not supported forCustom.
No sensor values parsed for topic: ...- Issue: The topic matched, but the payload could not be decoded.
- Resolution: For
Jsonsubscriptions, this usually means the JSON is malformed or the configured path does not exist in the payload.
Skipping JSON payload for Raw+Auto topic ...(debug-level)- Issue: A
Raw + Autosubscription received a JSON-shaped payload and skipped it. - Resolution: Switch the subscription to
Jsonencoding.
- Issue: A
A Sensor's Values Stopped Updating After a Configuration Changeโ
Look for the type-mismatch warning quoted in Data Type Locking. If you see it, the sensor's type was locked at creation and the new payloads are being dropped. Delete the sensor in MaintainX and let the agent recreate it on the next payload.