Designing Your Topic Tree
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.When to Reach for This
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.Common Principles Across Every UNS
Whatever your domain, a good hierarchy answers three questions:- Who owns this data? Top levels usually identify the organization or operator.
- Where does it physically live? Middle levels mirror the real-world layout — sites, buildings, zones, machines.
- What is it? The leaves identify the actual measurement, status, or event.
Functional namespaces
Organize topics into functional namespaces that reflect the data lifecycle. These prefixes work alongside any domain hierarchy (ISA-95, buildings, fleets):Designing for Your Domain
Different domains use different hierarchy patterns. Here are five common ones — pick the closest match to yours and adapt.Manufacturing — ISA-95
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
Smart Buildings
Buildings have a natural physical hierarchy: campus → building → floor → zone → device. The same operator prefix can branch like a folder tree: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 and Solar Parks
Energy operators typically organize by site, then by physical infrastructure (arrays, strings, inverters). One site can group weather separately from the array:solarop/lisbon-park/# for one park. A fleet operations center subscribes to solarop/+/+/+/+/power to monitor every inverter across every park.
Smart Cities
Cities organize by district, then by asset type (since asset categories are the meaningful grouping, not departments):lisboa/+/traffic_lights/#. An air quality dashboard subscribes to +/+/air_quality/# to see every monitor in every city.
Vehicle Fleets
Fleets organize by operator, region, vehicle type, and individual vehicle:deliveryco/iberia/#. A predictive-maintenance system subscribes to +/+/+/+/engine/diagnostic_codes across every fleet.
Optional: Data Classification
Many UNS designs add a classification segment after the device, splitting measurements, state, events, and alarms into separate branches under the same device:acmecorp/headquarters/+/+/+/alarms/#) without picking up a flood of ordinary readings. Use it when your project has clearly different kinds of data per device.
Don’t Over-Engineer Small Projects
A single-room weather station or a hobby solar setup doesn’t need six levels of hierarchy. Even a flatsensors/<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.
Choosing Data Formats
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.Scalar payloads (value only)
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.- 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.
JSON payloads (structured body)
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 toprocessed/(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.
Models (typed, consistent output)
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.
Triggered example — raw scalar in, structured JSON out:
- 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 MODELcan 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 JSONon every step — see Models overview.
How the three formats work together
A common pipeline:- Ingress — device sends JSON to
sensors/+/telemetry(or scalar to.../temperature). - Action — optional
GET JSON, validation, unit conversion. - Model — publishes stable JSON to
processed/+/formatted(or triggered Model from scalar sources). - Route — stores
processed/#or specific leaves in a database.
Standardizing Data Across Device Types
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.When to Reach for This
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.The Pattern
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’sCOLLAPSED, meaning it doesn’t publish on its own and exists only to be inherited:
FROM:
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 TRIGGERon 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 viaPUBLISH MODELfrom an Action.
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.
Next Steps
Working with JSON
GET JSON patterns, nested payloads, and PUBLISH MODEL workflows.
Model Inheritance
Deep dive on
FROM, base models, and extending schemas.
