Your topic tree is the backbone of every Coreflux project. It decides how Actions trigger, how Models publish, how Routes map data into databases, and how dashboards subscribe. Get it right early and the system stays clean as it grows. Get it wrong and you’ll be untangling spaghetti for years.A Unified Namespace (UNS) is the idea that every piece of data in your system has one predictable address — a single MQTT topic — that any consumer can subscribe to without knowing or caring where the data physically came from. The concept applies to any domain: a factory floor, an office tower, a solar park, a city’s traffic lights, or a fleet of delivery trucks.
Like a postal address that works anywhere in the world. “Country / State / City / Street / Building / Apartment” lets a letter find any home, even one the postman has never visited. A UNS does the same for your data — a fresh subscriber can read or write to any device without ever being told it exists.
Always — but the deeper hierarchy patterns earn their keep once you have more than a handful of devices, more than one site, or more than one team subscribing to the data. Even a single-device hobby project benefits from a consistent two-level structure.
Organize topics into functional namespaces that reflect the data lifecycle. These prefixes work alongside any domain hierarchy (ISA-95, buildings, fleets):
For factories, the industry standard is ISA-95, which divides operations from the enterprise level down to individual machines. The full MQTT path is a single string; the folder view below shows how each topic level lines up with ISA-95 (Enterprise → Site → Area → Line / Cell → Device → Metric at the leaf).acme/dallas/packaging/line1/filler01/temperature
acme/ enterprise└── dallas/ site └── packaging/ area └── line1/ line / cell └── filler01/ device └── temperature metric
The ISA-95 levels: Enterprise / Site / Area / Line / Cell / Device / Metric. Use it for any project where you have a corporate hierarchy that maps onto a physical plant layout.
Buildings have a natural physical hierarchy: campus → building → floor → zone → device. The same operator prefix can branch like a folder tree:
acmecorp/└── headquarters/ ├── floor3/ │ └── conference_a/ │ ├── thermostat/ │ │ └── temperature │ └── lighting/ │ └── state └── garage/ └── level_b1/ └── sensors/ └── co2
Subscribe to acmecorp/headquarters/floor3/# to see every reading on the third floor. Subscribe to acmecorp/+/+/+/thermostat/temperature to compare temperatures across every conference room in the company.
Energy operators typically organize by site, then by physical infrastructure (arrays, strings, inverters). One site can group weather separately from the array:
A maintenance team subscribes to solarop/lisbon-park/# for one park. A fleet operations center subscribes to solarop/+/+/+/+/power to monitor every inverter across every park.
A regional dispatcher subscribes to deliveryco/iberia/#. A predictive-maintenance system subscribes to +/+/+/+/engine/diagnostic_codes across every fleet.
Many UNS designs add a classification segment after the device, splitting measurements, state, events, and alarms into separate branches under the same device:
This makes it trivial to subscribe to “all alarms in the building” (acmecorp/headquarters/+/+/+/alarms/#) without picking up a flood of ordinary readings. Use it when your project has clearly different kinds of data per device.
A single-room weather station or a hobby solar setup doesn’t need six levels of hierarchy. Even a flat sensors/<device_id>/<metric> is fine until you have enough scale to need more. Pick a structure that fits today’s complexity, and leave room to grow.
Your topic tree answers where data lives. The next decision is what shape each message carries — a single value on the payload, a JSON object, or structured JSON produced by a Model. These choices are independent: you can mix scalar topics, JSON ingress, and Model output in the same project.
Like labels on shipping boxes. The address (topic) tells you which shelf it belongs on; the box contents (payload) tell you what’s inside. Sometimes one number on the label is enough; sometimes you need a packing list (JSON); sometimes you standardize the box size (Model) so every downstream handler knows what to expect.
Use a plain payload — one number, string, or boolean with no JSON wrapper — when the topic name already identifies the measurement and you only ever send one value per message.
DEFINE ACTION CheckHighTemperatureON TOPIC "acme/dallas/+/+/+/temperature" DO SET "temp" WITH PAYLOAD AS DOUBLE IF {temp} > 80 THEN PUBLISH TOPIC "alerts/high_temperature" WITH {temp}
Practices:
Put meaning in the topic path (site, device, metric) so subscribers do not have to parse the body.
Prefer scalar leaves under processed/ or your UNS metric level when data is already normalized at the edge.
Avoid cramming multiple measurements into one topic without JSON — use separate topics or switch to JSON.
Use JSON when one MQTT message carries several fields, nested data, or metadata (timestamps, units, quality flags) that do not belong in the topic string.
In Actions, extract fields with GET JSON. For nested paths, missing keys, and publishing cleaned JSON from an Action, see Working with JSON — that page is the hands-on guide; this section is about when to choose JSON at the data-layer level.Practices:
Keep raw JSON on ingress topics (sensors/, integrations/) and publish normalized data to processed/ (scalar or Model output) so dashboards and databases see a stable shape.
Always cast types in Actions (AS DOUBLE, AS STRING) — do not rely on implicit conversion.
If many consumers need the same JSON schema, promote the shape with a Model instead of rebuilding JSON manually in every Action.
A Model defines the JSON schema your broker publishes — field names, types, and triggers. Use Models when multiple subscribers (Routes, dashboards, other Actions) must see the same structure every time.
Approach
When to use
How it publishes
Triggered Model (WITH TOPIC + AS TRIGGER)
One device type, automatic publish when a source field updates
Broker builds JSON when the trigger field changes
COLLAPSED Model + PUBLISH MODEL from an Action
You control timing, destination, or merge JSON + scalar sources
Triggered example — raw scalar in, structured JSON out:
DEFINE MODEL SensorReading WITH TOPIC "acme/dallas/line1/filler01/formatted" ADD STRING "device_id" WITH "filler01" ADD DOUBLE "temperature" WITH TOPIC "acme/dallas/line1/filler01/temperature" AS TRIGGER ADD STRING "unit" WITH "celsius" ADD STRING "timestamp" WITH TIMESTAMP "UTC"
Action-driven example — JSON in, COLLAPSED Model out (full walkthrough in Working with JSON):
DEFINE MODEL ProcessedSensorReading COLLAPSED ADD STRING "device_id" ADD DOUBLE "temperature" ADD STRING "unit" ADD STRING "timestamp"DEFINE ACTION NormalizeTelemetryON TOPIC "sensors/+/telemetry" DO SET "sensor_id" WITH TOPIC POSITION 2 SET "temp" WITH (GET JSON "temperature" IN PAYLOAD AS DOUBLE) PUBLISH MODEL ProcessedSensorReading TO "processed/" + {sensor_id} WITH device_id = {sensor_id} temperature = {temp} unit = "celsius" timestamp = TIMESTAMP "UTC"
Practices:
Use scalar topics for simple ingestion; use Models for the contract your analytics and APIs depend on.
Do not hand-build JSON strings in Actions when PUBLISH MODEL can do it — fewer quoting bugs and consistent field names.
Mark the primary measurement as AS TRIGGER, not timestamps or metadata (schema definition).
Exploding incoming JSON into per-field topics (Models overview) helps when other Models or Actions need single fields without GET JSON on every step — see Models overview.
When several devices in your system share most of their data fields but each has a few extras, define the shared fields in a base Model, then extend it for each variant. A LoT Model is essentially a typed schema that publishes structured JSON onto a topic. Inheritance works exactly like in object-oriented programming — the base holds what’s common, the children add what’s specific. Full reference: Model Inheritance.
Like a base recipe for bread dough. Sourdough, focaccia, and pizza all start from the same base — they just add their own toppings. Improve the base recipe, and every variant gets the improvement automatically.
When your system has more than one kind of device that mostly produces the same data — pumps and chillers, vans and trucks, conference rooms and labs. The break-even point is around three variants: at two it’s still tempting to copy-paste, at three the inheritance pays off, at five it’s the only sane way.
A smart-building example: every room in the building has temperature, humidity, and occupancy sensors. But conference rooms also need to track presentation equipment, and labs need extra safety monitoring. Start with the shared base — it’s COLLAPSED, meaning it doesn’t publish on its own and exists only to be inherited:
A standard office uses the base directly. Specialized rooms extend it using FROM:
DEFINE MODEL ConferenceRoom FROM BaseRoom ADD BOOL "projector_on" ADD BOOL "video_call_active" ADD INT "scheduled_meetings"DEFINE MODEL Lab FROM BaseRoom ADD BOOL "fume_hood_running" ADD DOUBLE "co2_ppm" ADD STRING "hazmat_status"
ConferenceRoom automatically has all six fields from BaseRoom plus three of its own. Lab gets the six base fields plus three lab-specific ones. If you later add a last_cleaned field to BaseRoom, every child Model gains it without any edits.Two rules of thumb for designing the base:
Put a field in the base only if every variant truly needs it. Identifiers, timestamps, status, and core measurements belong in the base. Type-specific fields belong in the children.
Don’t put AS TRIGGER on a base Model field. The base exists to be inherited — it’s the children that decide when and how they publish, either through their own trigger or via PUBLISH MODEL from an Action.
The payoff: every team consuming room data — facilities dashboards, energy management, AI agents — sees a consistent shape. They can rely on room_id, temperature, and occupancy being there for any room, and only need to handle the extra fields for variants they actually care about.The same pattern fits anywhere you have a family of related devices: BaseInverter with grid-tied and hybrid variants in solar, BaseSensor with temperature, vibration, and pressure variants in industrial, BaseVehicle with van, truck, and refrigerated variants in fleets.For JSON-heavy ingress before you standardize with Models, see Choosing data formats and Working with JSON.