DTDL, the Digital Twin Definition Language, is a JSON-based way to describe what a digital twin is made of: the telemetry it emits, the properties it holds, the commands it accepts and how it relates to other twins. In this article you will learn how DTDL models are structured, read a complete example, and see how to map that model onto MQTT topics so devices and twin applications share one clear contract.
For the bigger picture of where models fit, see our digital twin technology guide.
What DTDL Is and Why It Exists
When ten teams build twins independently, each invents its own way to say "this pump has a flow rate in litres per minute and a start command". DTDL standardises that description. It is an open language published by Microsoft, based on JSON-LD, and used by Azure Digital Twins and IoT Plug and Play, but nothing in the language itself ties you to one cloud. A DTDL file is just a document describing an asset type; you can use it to generate code, validate payloads, build UIs or document topic contracts.
The language has gone through versions; DTDL v3 is the current one, with v2 still widely seen in existing projects. The official specification lives in the DTDL GitHub repository.
The Building Blocks of a DTDL Model
Everything in DTDL is defined inside an Interface. An interface has an identifier and a list of contents, each of which is one of these element types:
| Element | What it describes | Example |
|---|---|---|
| Telemetry | A stream of measurements, not stored as state | Temperature every second |
| Property | A state value, optionally writable | Firmware version, setpoint |
| Command | An operation the twin can be asked to perform | Start, stop, reset counter |
| Component | Reuse of another interface inside this one | A pump containing a motor interface |
| Relationship | A link to another twin | Pump isPartOf station |
Each model and element has a DTMI (Digital Twin Model Identifier), such as dtmi:com:example:Pump;1. The trailing number is the version. When you change a model in an incompatible way, you increment the version rather than editing it in place.
Telemetry vs Property
This is the distinction teams most often get wrong. Telemetry is a stream of events: you care about each sample, and there is no "current value" stored by the twin platform. Properties are state: the twin always has a current value, and a writable property can be set from the cloud side (a desired value) with the device reporting back its actual value.
Rule of thumb: if a new viewer needs to see it immediately on load, it is a property; if it only matters as a series over time, it is telemetry.
A Complete DTDL Example
Here is a DTDL v3 interface for a centrifugal pump:
{
"@context": "dtmi:dtdl:context;3",
"@id": "dtmi:com:example:Pump;1",
"@type": "Interface",
"displayName": "Centrifugal Pump",
"contents": [
{
"@type": ["Telemetry", "VolumeFlowRate"],
"name": "flow",
"schema": "double",
"unit": "litrePerSecond"
},
{
"@type": ["Telemetry", "Pressure"],
"name": "dischargePressure",
"schema": "double",
"unit": "bar"
},
{
"@type": "Property",
"name": "running",
"schema": "boolean"
},
{
"@type": "Property",
"name": "speedSetpoint",
"schema": "double",
"writable": true
},
{
"@type": "Property",
"name": "firmwareVersion",
"schema": "string"
},
{
"@type": "Command",
"name": "resetRunHours"
},
{
"@type": "Relationship",
"name": "isPartOf",
"target": "dtmi:com:example:PumpingStation;1"
}
]
}
Semantic types like VolumeFlowRate and units like litrePerSecond come from the DTDL quantitative types extension. They let tools render units correctly without guessing. Check the specification for the exact list of semantic types and units valid in the version you target.
Mapping DTDL to MQTT Topics
DTDL describes what a twin contains; it does not say how data travels. MQTT is a natural transport, and a clean mapping turns your DTDL model into a topic contract. One practical convention:
| DTDL element | MQTT topic | Retained | QoS |
|---|---|---|---|
Telemetry flow |
<asset>/telemetry (JSON with all telemetry fields) |
No | 0 |
| Property (read-only) | <asset>/properties/reported |
Yes | 1 |
| Writable property desired value | <asset>/properties/desired |
Yes | 1 |
Command resetRunHours |
<asset>/commands/resetRunHours |
No | 1 |
| Command response | <asset>/commands/resetRunHours/response |
No | 1 |
| Relationship | Stored in the model or registry, not on the wire | n/a | n/a |
With <asset> being an ISA-95 path like acme/site1/water/station3/pump2, a viewer can subscribe to acme/site1/water/# and know, from the model, how to interpret every message.
Why combine telemetry into one message per asset? Publishing one topic per telemetry field multiplies message count without adding information, and most consumers want all fields at the same timestamp. Properties benefit from retained messages so twin viewers get current state instantly; the digital twin retained MQTT tutorial shows this in practice.
Validating Payloads Against a DTDL Model
Once you have a model, use it. A small Python check can reject telemetry that does not match the interface before it pollutes your history:
import json
import paho.mqtt.client as mqtt
with open("pump.json", encoding="utf-8") as f:
model = json.load(f)
TYPE_MAP = {"double": (int, float), "boolean": bool, "string": str, "integer": int}
telemetry = {
c["name"]: c["schema"]
for c in model["contents"]
if "Telemetry" in (c["@type"] if isinstance(c["@type"], list) else [c["@type"]])
}
def on_connect(client, userdata, flags, reason_code, properties):
client.subscribe("connectmq/demo/d41f/+/+/+/+/telemetry")
def on_message(client, userdata, msg):
data = json.loads(msg.payload)
for name, schema in telemetry.items():
if name not in data:
print(f"{msg.topic}: missing {name}")
elif not isinstance(data[name], TYPE_MAP[schema]):
print(f"{msg.topic}: {name} should be {schema}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="dtdl-validator-d41f")
client.on_connect = on_connect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883)
client.loop_forever()
Test it by publishing a sample message to the public MQTT test broker:
mosquitto_pub -h broker.connectmq.com -p 1883 -i dtdl-pub-d41f \
-t "connectmq/demo/d41f/acme/site1/station3/pump2/telemetry" \
-m '{"flow": 12.4, "dischargePressure": "4.1"}'
The validator flags dischargePressure as a string instead of a double, the kind of bug that otherwise surfaces weeks later as a broken chart.
DTDL Modelling Checklist
- One interface per asset type, not per asset instance.
- Telemetry for streams, properties for state, commands for actions.
- Use components for reusable parts like motors or sensors.
- Include units through semantic types wherever possible.
- Version DTMIs and never break a published version.
- Document the MQTT mapping next to the model in the same repository.
- Validate payloads at ingestion, not only in the UI.
DTDL Compared with Other Twin Models
DTDL is not the only option. The Asset Administration Shell from the Industry 4.0 world focuses on standardised submodels for industrial assets and is common in European manufacturing; see Asset Administration Shell explained. Eclipse Ditto uses its own JSON "things" with attributes and features. Many teams also use a plain JSON Schema per asset type. What matters most is having one explicit model and a documented mapping to the transport, which is the role DTDL plays well. For how the model layer sits next to messaging, state and visualization, see digital twin architecture.
Running Your DTDL-Based Twin on ConnectMQ
DTDL defines the contract; you still need a reliable transport. ConnectMQ is a managed MQTT broker supporting MQTT 3.1.1 and 5.0 with retained messages for properties, TLS on 8883 for devices and WSS on 8084 for browser twins. Model validation, code generation and twin graphs run in your own tools, connected to ConnectMQ as regular MQTT clients. Create a broker on the free Serverless plan at app.connectmq.com, no credit card needed, and publish your first DTDL-shaped telemetry in minutes.
Frequently Asked Questions
Is DTDL only for Azure?
No. DTDL is an open, JSON-LD based language. Azure Digital Twins uses it natively, but you can use DTDL models with any transport, including MQTT on any broker.
What is the difference between a DTDL property and telemetry?
A property is state with a current value, optionally writable; telemetry is a stream of measurements where each sample matters but no current value is stored.
How do I represent DTDL commands over MQTT?
Publish the command to a non-retained command topic at QoS 1 with a correlation ID, and have the device publish the result to a response topic. MQTT 5 response topic and correlation data properties support this directly.
Which DTDL version should I use?
Use DTDL v3 for new projects unless a tool you depend on only supports v2.



