Skip to main content

MQTT Subscription Reference

availability
Feature availability by platform, plan type, and user type
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:

SubscriptionTypeWhen to Use
CustomAny MQTT broker. You control topic patterns, payload decoding, and field mapping.
SparkplugBrokers using the Sparkplug B specification.
Zigbee2MQTTBrokers 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:

FieldRequiredDescription
TopicYesThe MQTT topic pattern. It supports the + wildcard per segment. The # wildcard isn't supported for Custom subscriptions. For details, see Wildcard Rules.
SubscriptionTypeYesThis field must be set to Custom for the configurations described on this page.
PayloadYes (for Custom)An object that describes how to decode the payload. For details, see Payload Encoding.
DescriptionNoA free-text annotation. It has no effect on OPA behavior and is useful for documenting your configuration.
TimestampFieldNoThe 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:

FieldRequiredDescription
VersionYesThe schema version. Set this to "v1".
SubscriptionsYesAn array of subscription objects (at least one is required).
IgnoredTopicsNoAn array of topic patterns to exclude from processing. For details, see Ignored Topics.

Payload Encoding​

The Payload.Encoding field selects one of two decoders:

EncodingWhen to Use
RawEach topic carries a single scalar value (a number, a boolean, or a string).
JsonThe 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:

FieldRequiredDescription
EncodingYesThe encoding type. It must be set to "Raw".
TypeYesThe data type. It must be one of "String", "Number", "Boolean", or "Auto". For details, see Type Auto: Automatic Type Inference.
FriendlyNameNoThe name to use for the tag in MaintainX, subject to the Tag Naming Matrix.
UnitNoThe unit of measurement (e.g., °C or psi). This applies only when Type resolves to a numeric value.
DescriptionNoA 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:

FieldRequiredDescription
EncodingYesThe encoding type. It must be set to "Json".
FieldsYesAn array of 1 to 1,000 field mappings. Each field mapping becomes a tag.

Each entry in Fields accepts:

FieldRequiredDescription
PathYesThe dot-separated path to the value (e.g., data.temperature). Array indexes are supported using square brackets (e.g., data.readings[0].value).
TypeYesThe data type. It must be one of "String", "Number", "Boolean", or "Auto".
FriendlyNameNoThe name to use for the tag. It's subject to the Tag Naming Matrix.
DescriptionNoA free-text description shown on the tag. This field is mutually exclusive with DescriptionPath.
DescriptionPathNoThe JSON path used to read the description from each payload. This field is mutually exclusive with Description.
UnitNoThe static unit of measurement. This applies only to numeric types. This field is mutually exclusive with UnitPath.
UnitPathNoThe 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:

  1. Boolean: Values that parse as true/false (case-insensitive).
  2. Number: Any value that parses as a decimal number, including integer-looking strings, such as 42.
  3. String: Anything else not covered by Boolean or Number values.

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 TopicPayloadTag NameInferred Type
factory/line1/oven_temp185.4oven_tempNumber
factory/line1/door_opentruedoor_openBoolean
factory/line1/stateRUNNINGstateString

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​

warning

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:

  1. Delete the tag in MaintainX.
  2. 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_temp has lastSegment = "oven_temp".
  • A message on factory/line1/door_open has lastSegment = "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 PatternFriendlyNameResulting Tag Name
No trailing wildcardSetFriendlyName
No trailing wildcardNot setlastSegment
Middle + onlySetFriendlyName
Middle + onlyNot setlastSegment
Trailing +(rejected)Configuration is rejected at startup. For details, see Wildcard Rules.

Naming for JSON Encoding​

Topic PatternNumber of FieldsFriendlyNameResulting Tag Name
No trailing wildcard1SetFriendlyName
No trailing wildcard1Not setlastSegment
No trailing wildcard2 or moreSetlastSegment/FriendlyName
No trailing wildcard2 or moreNot setlastSegment/Path
Trailing +1AnylastSegment (FriendlyName is ignored)
Trailing +2 or moreSetlastSegment/FriendlyName
Trailing +2 or moreNot setlastSegment/Path
info

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 TopicTag Name
plant/boiler/temperatureBoiler Temperature

Example 2: Raw + Auto With Trailing Wildcard​

{
"Topic": "factory/line1/+",
"SubscriptionType": "Custom",
"Payload": {
"Encoding": "Raw",
"Type": "Auto"
}
}
Received TopicTag NameInferred Type
factory/line1/oven_tempoven_tempNumber
factory/line1/door_opendoor_openBoolean

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 TopicTag Names
plant/line1/tagstags/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 TopicTag Names
plant/line1/readingsreadings/temp, readings/pressure
plant/line2/readingsreadings/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 SupportedUse 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​

warning

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.