Docs/Timebase Components/How Binding Works: Properties, Instances and Data Sources
AtlasConcept

How Binding Works: Properties, Instances and Data Sources

How a property in an Atlas model is connected to a live value from a data source, and where the binding path comes from.

Updated 6 Nov 2026Timebase Docs

Overview#

If you searched our documentation for "entity binding" and arrived here, that term is not an Atlas term - but the capability you are looking for exists, and you are probably asking one of two different questions. This article answers both and routes you to the right next step.

Atlas distinguishes two separate things: where individual property values come from, and how the entities themselves are created. In Atlas terminology, binding is a property-level concept. You bind a property to a data source. You do not bind an entity. If you are looking for "entity binding", you really mean one of these:

  1. Property binding - a single property on an instance reads a live value from a connector.
  2. Instance generation from a data source - the instances themselves are created from rows a connector returns, rather than being defined by hand.

These are handled by different Atlas features, and you need different tools for each.

You want to... It is called in Atlas... Read this article...
Give an instance a property that reads a live sensor value, tag, or database field Bound strategy with a binding path Property Sourcing Strategies - Static, Bound, and Calculated
Create the instances themselves from rows in an asset register, equipment list, or connector query result RowsetTemplate mode in an instance map Instance Maps and Instance Maps Programming Guide

Pick the row that matches what you are trying to do, then follow its link. The rest of this article explains how the two relate to each other and what you need to know to use them together.

Property binding: the Bound strategy#

A property gets its value from somewhere. The place it gets it from is called its sourcing strategy. Atlas recognizes six strategies, three of which you choose directly in the browser: Static (a fixed value you type in), Bound (a live connector value), and Calculated (a formula based on other properties). The Bound strategy is the one that connects to external systems.

When you use the Bound strategy on a property, you tell Atlas two things:

  1. This property receives its value from a Connector - a component that talks to an external system.
  2. The exact location of that data is the binding path - a slash-delimited address like historian-juice-factory/The Juice Factory/131-TT-01.PV.

That is property binding. One property binds to one data point. You do it in the Mappings tab of an instance map, on a property-by-property basis.

Where binding paths come from#

Every connector advertises the data points it can serve, in the form of binding paths. The Timebase Historian connector, for example, advertises paths like {InstanceId}/{Dataset}/{TagName}. The OPC UA connector advertises {InstanceId}/{BrowsePath}. You copy these paths into your property definitions. You never invent them.

The rule: copy the path the connector advertises, and write it as plain slash-delimited segments. A scheme:// prefix such as historian:// or opc:// does not match any advertised path.

Two plugins differ: the HTTP/REST connector advertises the endpoint's configured path with no instance-id prefix, and the Timebase Reference connector advertises fixed fake/... paths. For those two, type the path exactly as advertised.

Copy the path rather than typing it#

A binding path is matched against the connector's advertised paths at start-up rather than when you save, so a path that does not match is simply not subscribed and the property holds its default value (0, 0.0 or 0001-01-01, depending on type).

That makes a single character - 131-TT-1.PV where the tag is 131-TT-01.PV - the difference between a live property and an empty one.

Do not guess a path. Get it from:

  • An existing working Bound property on the same connector. Open its mapping and copy the path.
  • The source system itself - the exact dataset and tag name as they appear in the Timebase Historian, the browse path as it appears in OPC UA, the SQL table and column name, etc.

Paths are case-sensitive. 131-TT-01.PV is not the same as 131-tt-01.pv.

Reading an unsampled property: no value is not zero#

Until the first sample arrives from a connector, a property holds no value, so it reads as null:

  • Numbers read as null
  • Date-time fields read as null
  • Quality fields read as null

This happens in ordinary circumstances: the property is subscribed but no update has arrived yet, the source tag is offline, or the connector is down. A source that sends a null cell leaves the property with no value in the same way.

A written zero is a value. A source that delivers 0.0 reads as 0.0, and a Quality of 0 (Bad) is a reading that arrived, so neither is mistaken for "never sampled".

For a compound property such as atlas-common:TVQ, the whole property is null before its first sample, so WHERE n.Temperature IS NULL finds the instances that have never received one. Once any field holds a value the property is a compound again; to test one field, write WHERE n.Temperature.Value IS NULL.

Give a bound property a compound type#

A bound property needs a compound data type such as atlas-common:TVQ, the Timestamp-Value-Quality record, rather than a bare scalar. For a plain scalar property, Atlas reads the first column by position, so binding one to a [Timestamp, Value, Quality] rowset lands the Timestamp in it and the property reads as a date.

Give a bound property a compound type with room for every field the connector sends. The connector fills each field by matching its column names to the compound's field names, so the names must match exactly, including case.

When you read a compound property in a query, navigate to the field you want:

MATCH (p:Pump) RETURN p.Speed.Value, p.Speed.Timestamp, p.Speed.Quality

Not p.Speed on its own. Property and field names are case-sensitive.

Instance generation: the RowsetTemplate pattern#

Separate from property binding is the question of where the instances themselves come from. Do you define them by hand (a few named things), or do you want Atlas to create them from a list that comes from a data source?

An instance map definition can expand in three ways:

Shape What it produces Typical use
Singleton Exactly one instance A single site, a central control system, a company-level asset
StaticTemplate Multiple named instances from values you type in 12 fillers, 6 pumps, 5 reactors - a fixed list you know at design time
RowsetTemplate One instance per row a connector returns Pump instances created from an asset register, pump stations from a SQL asset hierarchy table

You do not pick the shape directly. Atlas infers it: if your instance map has a Source block, it is a RowsetTemplate. If you have variables but no source, it is a StaticTemplate. Otherwise it is a Singleton.

RowsetTemplate mode and the compile-time connector dependency#

RowsetTemplate is the one that connects to a data source. When you define a RowsetTemplate, you tell Atlas: "read this connector path and create one instance per row it returns."

Here is the crucial difference from property binding: RowsetTemplate reads the connector at compile time, not at read time. Atlas fetches the rowset once, applies any transforms, and creates one instance per surviving row. A Bound property can subscribe to a path whose data has not arrived yet and simply wait. A RowsetTemplate cannot wait, because it needs the rows to know how many instances to create.

What that means in practice:

  • When you save, Atlas checks that the source path resolves through a registered connector. A path that does not resolve is reported as rule I-B10.
  • If the connector returns no rows when you compile, or every row is dropped by a transform, the definition produces zero instances. Atlas logs a warning and the compile still reports success. Anything that refers to those instances, such as a relationship or a Reference strategy, then fails validation instead.
  • When Atlas starts, a domain with RowsetTemplate sources is deferred, not compiled empty, until its connector advertises the source path. Atlas then compiles it on its own.

Consequence: a RowsetTemplate domain has a live dependency at compile time. A Singleton or StaticTemplate domain does not. If you author a domain that uses RowsetTemplate, plan for that connector to be available when you compile.

Placeholders inside a RowsetTemplate#

The instance map definition supports [Name] placeholders in the instance id, label, and property values. Each placeholder is a variable from the map list. In a RowsetTemplate, a variable's data is a column reference written [sourceName.columnName], so each variable takes its value from the current row.

Suppose a SQL Server source in Connect, with the alias asset-register-sql, has a query topic Pumps that runs SELECT PumpId, PumpName, Capacity FROM dbo.Equipment WHERE Type = 'Pump'. Connect serves that query at the path asset-register-sql/Pumps, and one definition turns its rows into pumps:

{
  "id": "pumps-from-register",
  "name": "Pumps from Asset Register",
  "type": "urn:atlas:ontology:plant:Pump",
  "instanceId": "pump-[PumpId]",
  "label": "[PumpName]",
  "sources": {
    "register": { "bindingPath": "asset-register-sql/Pumps" }
  },
  "map": [
    { "name": "PumpId",   "data": "[register.PumpId]" },
    { "name": "PumpName", "data": "[register.PumpName]" },
    { "name": "Capacity", "data": "[register.Capacity]" }
  ],
  "properties": {
    "urn:atlas:ontology:plant:Capacity": [
      { "type": "StaticStrategy", "value": "[Capacity]" }
    ],
    "urn:atlas:ontology:plant:Speed": [
      { "type": "BoundStrategy", "bindingPath": "historian-plant/MainLine/Pump-[PumpId]-Speed" }
    ]
  }
}

Here, [PumpId], [PumpName], and [Capacity] are filled in once per row. The result is one instance per row, with identities like pump-101 and pump-102.

Bound properties can also use placeholders. A binding path that includes [PumpId] resolves to a different path for each instance. This is how one definition binds 200 pumps to 200 different sensor tags.

The source block takes only two things: the bindingPath to read and an optional set of transforms. There is no query, filter or sort in Atlas. Any narrowing happens in the connector, through the topic's SQL, filter or browse scope, or in a transform, described under Using connector metadata in instance maps.

Using both together: instance generation plus property binding#

The two features work together seamlessly. You can use a RowsetTemplate to create instances, and then bind properties on those instances to a connector:

"properties": {
  "urn:atlas:ontology:plant:Speed": [
    { "type": "BoundStrategy", "bindingPath": "historian-plant/MainLine/Pump-[PumpId]-Speed" }
  ]
}

Each instance created from the rowset will have a Speed property bound to a different path, resolved by filling in the [PumpId] placeholder for that instance. One definition creates the structure and populates both the instances and the bindings.

Alternatively, you can create instances with StaticTemplate or Singleton, and add Bound properties later without touching the instance definition. Property binding is independent of how the instances were created.

Using connector metadata in instance maps#

A connector's catalog path is a rowset like any other, but its rows describe the source instead of measuring anything: one row per data point the connector serves, with columns such as the name, the description and the engineering unit. A RowsetTemplate over a catalog path can therefore create your instances and fill their metadata from the source system, so nobody has to type a unit or a description into the model.

There are three things you might want from a source, and each one has its own mechanism:

You want Use
The source's values (a daily total, a sensor reading) A Bound property on a value path, never on the catalog
The source's metadata as property values in Atlas (a unit, a description, a display format you can query and display) A RowsetTemplate over the catalog path, described in this section
The source's metadata inside your library definitions (a property's own description and unit annotation) Author it into the library, reading the catalog as your source of truth. It is copied once, at design time, and it does not stream

Which connectors offer metadata#

Not every connector has a catalog. This table lists what each plugin can give a RowsetTemplate:

Connector Path that returns metadata rows Columns you can map Details
Timebase Historian <alias>/<topic key>, one per entry under Datasets name, description, format, dataType, uom, fields. uom and fields are JSON objects held in a string Timebase Historian Connector
InfoHub <alias>/<reference key> id, kind, name, description, unit, intervalType, retrievalType, calendars, eventSchemes, fields (JSON object), attributes (JSON array) InfoHub Connector
AVEVA Historian <alias>/<topic> name, description, unit (analog tags only) AVEVA Historian Connector
AVEVA PI System <alias>/<topic> name, description, unit AVEVA PI System Connector
Canary Labs Historian <alias>/<topic> name, description, unit, dataType Canary Labs Historian Connector
SQL Server, PostgreSQL, MySQL, Oracle <alias>/<topic key>, one per query Whatever your SELECT returns. Point a query at an asset register or a tag metadata table Microsoft SQL Server Connector and its siblings
HTTP/REST One path per endpoint The columns you configure on the endpoint, so an endpoint that returns a list of assets can drive a map HTTP Connector
OPC UA, CESMII i3X, Reference None These plugins advertise one value path per node or object and return only time-series columns. They have no metadata rows, so their units and descriptions cannot drive a map. Use a StaticTemplate and author the metadata as static values The connector's own article

Column names are case-sensitive. Preview the catalog path in Connect to see the exact names and values before you write the map.

The tag pattern: metadata and value from one definition#

The catalog row carries the tag name, and the tag name is what fills the value path. So one definition can create a tag entity per catalog row, copy its metadata into static properties, and bind its value, all at once. This is the pattern the shipped example domains use, on the atlas-common tag classes:

{
  "id": "tags-WI-WTG-WindSpeed",
  "name": "Wind turbine wind speed tags",
  "type": "urn:atlas:ontology:atlas-common:AnalogTag",
  "instanceId": "WI-WTG-[Num]-WindSpeed",
  "label": "[Descr]",
  "sources": {
    "catalog": {
      "bindingPath": "historian-wind/Wind",
      "transforms": {
        "name": { "regex": "^710-SIT-(?<Num>\\d{3})\\.PV$" },
        "uom":  { "regex": "^(?:.*?\"1\"\\s*:\\s*\"(?<Unit>[^\"]*)\")?" }
      }
    }
  },
  "map": [
    { "name": "Num",   "data": "[catalog.Num]" },
    { "name": "Tag",   "data": "[catalog.name]" },
    { "name": "Descr", "data": "[catalog.description]" },
    { "name": "Unit",  "data": "[catalog.Unit]" },
    { "name": "Fmt",   "data": "[catalog.format]" }
  ],
  "properties": {
    "urn:atlas:ontology:atlas-common:Description": [{ "type": "StaticStrategy", "value": "[Descr]" }],
    "urn:atlas:ontology:atlas-common:UOM":         [{ "type": "StaticStrategy", "value": "[Unit]" }],
    "urn:atlas:ontology:atlas-common:Format":      [{ "type": "StaticStrategy", "value": "[Fmt]" }],
    "urn:atlas:ontology:atlas-common:Data":        [{ "type": "BoundStrategy",  "bindingPath": "historian-wind/Wind/[Tag]" }]
  }
}

Read it from the top:

  • The name transform keeps only the wind speed tags and captures the turbine number as Num, which builds the instance id.
  • The uom transform pulls the unit out of the JSON column, explained in the next section.
  • Descr, Fmt and Unit become static property values, and Tag fills the value path of the Bound Data property.

Equipment entities then reach these tags through a relationship and a Reference strategy, so the unit and description live in one place. Using the Example Domains shows the whole chain, including the Connect source the paths depend on.

Getting a value out of a JSON column#

Some catalog columns hold a JSON object rather than a single value: the Timebase Historian's uom and fields, and InfoHub's fields and attributes. Atlas has no JSON path syntax in a map. Instead, use a transform whose regular expression captures the part you want.

The Historian's uom column is a dictionary keyed by value. An analog tag usually keeps its unit under the key "1", as in {"1":"°F"}. A state or enumeration tag uses the keys for its states instead, as in {"0":"Idle","20":"Running"}. The transform above captures the text after "1": as Unit.

A fields value works the same way. For a Historian tag whose fields column is {"Area":"Filling","Instrument Code":"TT"}, this transform captures the instrument code as Code:

"fields": { "regex": "^(?:.*?\"Instrument Code\"\\s*:\\s*\"(?<Code>[^\"]*)\")?" }
Important

Every transform must match for a row to survive, and a row that fails any one of them is dropped. A unit regex written as a plain "1":"(?<Unit>...)" silently removes every tag that has no unit. The ^(?: ... )? wrapper used above makes the whole pattern optional, so it always matches: the row survives, and the capture is empty when the key is missing. Use a strict regex only where dropping the row is what you want, as with the name filter.

Rules worth knowing#

  • Metadata is copied at compile time. The values are read once, when the domain compiles, and stored as static values. If someone changes a unit or description in the source system, recompile the domain to pick it up.
  • An empty cell becomes an empty string. A null description or format produces an empty static value, not a missing property.
  • One transform per column. Transforms are keyed by column name, so a column can carry only one regex. Put several named groups in that one regex if you need several parts of the same column.
  • Captures are new columns. Reference a capture the same way as a real column, as [sourceName.CaptureName]. Give captures names that differ from the real columns.
  • One source per definition. A definition may declare several sources, but its map may reference only one of them. To combine two catalogs, write one definition per catalog and relate the results.
  • Transforms use .NET regular expressions. Named groups are written (?<Name>...), and a backslash in the JSON file is written twice.

An InfoHub measure dictionary#

The same approach works for any connector in the table. With the InfoHub catalog, a definition can materialise one instance per Measure, with its interval type, description and unit as plain properties:

{
  "id": "measure-dictionary",
  "name": "InfoHub Measure Dictionary",
  "type": "urn:atlas:ontology:plant:MeasureDefinition",
  "instanceId": "measure-[MeasId]",
  "label": "[MeasName]",
  "sources": {
    "catalog": {
      "bindingPath": "infohub-1/production",
      "transforms": { "kind": { "regex": "^Measure$" } }
    }
  },
  "map": [
    { "name": "MeasId",   "data": "[catalog.id]" },
    { "name": "MeasName", "data": "[catalog.name]" },
    { "name": "Interval", "data": "[catalog.intervalType]" },
    { "name": "Descr",    "data": "[catalog.description]" },
    { "name": "Uom",      "data": "[catalog.unit]" }
  ],
  "properties": {
    "urn:atlas:ontology:plant:IntervalType": [{ "type": "StaticStrategy", "value": "[Interval]" }],
    "urn:atlas:ontology:plant:Description":  [{ "type": "StaticStrategy", "value": "[Descr]" }],
    "urn:atlas:ontology:plant:Unit":         [{ "type": "StaticStrategy", "value": "[Uom]" }]
  }
}

The kind transform keeps only Measure rows, because an InfoHub catalog also lists Tags and event schemes. The target type's properties are plain literals (strings, numbers) rather than TVQ: these are facts about a measure, not measurements.

Because the catalog row also carries the ids that fill value-path placeholders, the same definition can bind values and stamp metadata side by side: a BoundStrategy on infohub-1/Folder/5/[MeasId]/Calendar/1 next to the StaticStrategy entries gives every materialised measure its live daily value and its unit, description and interval type in one definition.

Connectors: where the paths come from#

This is not the place for a deep dive into connectors - see Connecting Data Sources for the full guide. But in brief: a Connector is a component that talks to an external system and advertises the data points it can serve. Atlas connects to Connect over a long-lived network connection. Connect tells Atlas which paths it can serve, Atlas uses those paths in Bound properties and RowsetTemplate queries, and Connect pushes values in as they change.

The connector plugins that ship with Atlas can serve:

  • Timebase Historian
  • OPC UA servers
  • SQL databases (SQL Server, PostgreSQL, MySQL, Oracle)
  • i3X servers
  • AVEVA Historian and PI System
  • HTTP/REST APIs
  • Canary Labs Historian
  • InfoHub

For MQTT, Sparkplug B, Telegraf, and Collector-based OPC UA, the designed path is to use the Timebase Collector, which ingests those protocols and stores into a Timebase Historian that Atlas then connects to. This gives you store-and-forward buffering and full stored history. See Connecting Data Sources for the full reference.

When to use property binding versus instance generation#

Use Bound properties when:

  • You have a fixed set of instances (known at design time or defined by hand) and you want their current values to stream in from a live system.
  • An instance's values change frequently without human intervention (sensors, PLC readings, database updates).
  • Different instances read from different places (pump 1 from tag Pump-1-Speed, pump 2 from tag Pump-2-Speed).

Use RowsetTemplate when:

  • The instances themselves come from a list or a query result (an equipment register, an asset hierarchy, a production line catalog).
  • The list changes and you want new instances created automatically when new rows arrive.
  • You are building a bulk definition from a data source and want placeholders to fill in per-instance details.

Use both together when:

  • You want instances to be auto-generated from a data source AND their properties to read from a connector. This is the most common pattern in production.

Common mistakes#

What happens Why What to do
A Bound property never shows a value, even though the connector is connected The binding path is wrong. Paths are not validated at author time Compare the path character-for-character against a working Bound property on the same connector. Check for typos, case differences, missing spaces, or an erroneous scheme:// prefix. Correct it and recompile
A Bound property on a historian tag whose name contains / (such as an Ignition tag path) never shows a value, and nothing is logged In 2.0.0 the tag part of a historian path is a single segment, so the path is never routed. Escaping does not help: %2F and square brackets are not decoded From the first release after 2.0.0 (Atlas and Connect), write the tag exactly as named, slashes included. See Tag names that contain a slash
A brand-new Bound property shows a null value, timestamp and quality No sample has arrived yet. A property holds no value until something writes one Confirm the source tag is live in the source system. Check the Connectors panel on the Home page to confirm the connector is running. If other properties on the same connector are updating but this one is not, treat it as a binding-path problem
A Bound scalar property shows what looks like a date (like 44143 or 2026-08-10) The property is bound to a multi-column rowset but is declared as a scalar. Atlas reads the first column by position, which is the Timestamp Change the property's data type to a compound such as atlas-common:TVQ, or bind to a path that returns a single column
A Compound Bound property updates some fields but leaves others blank The connector's column names do not match the compound type's field names. Binding matches by name, not by position Check the exact column names the connector emits (including case) and the exact field names in the compound type. They must match exactly
A RowsetTemplate definition compiles but creates zero instances The source path returned no rows, or every row was dropped by a transform. Both are logged as warnings, not errors Preview the source path in Connect to confirm it returns rows. Check the Atlas log for transforms.<source> lines, which say how many rows each transform dropped
A RowsetTemplate creates fewer instances than the catalog has rows A transform did not match some rows, and a row that fails any transform is dropped. A unit or fields regex is the usual cause, because some tags have no unit Make metadata captures optional with the ^(?: ... )? wrapper described under Getting a value out of a JSON column
Saving a RowsetTemplate reports rule I-B10 The source path does not resolve through any registered connector: the connector is not running, or the alias or topic key is wrong Check that the connector is advertising the path. The first segment must be the Connect source's alias
Two Bound properties on the same connector show values from the wrong places Two connectors are advertising the same binding path. The last one to register wins Check the Connectors panel on the Home page. Ensure each connector instance has a unique instance id so its paths are distinct. If two truly need to serve the same data, check the Atlas log for the overlap warning

Where to go next#

For the complete details on each concept:

  • Property Sourcing Strategies - Static, Bound, and Calculated covers the six strategy types, the exact format of a binding path, compound properties, quality values, calculated formulas, and a complete troubleshooting table.
  • Instance Maps covers the three expansion shapes, creating instances in the browser, the Instance Builder view, and the compile workflow.
  • Instance Maps Programming Guide covers the JSON file format, the definition record, expansion arithmetic, and API routes for developers who generate definitions from a script.
  • Connecting Data Sources covers Connect, which connectors ship with Atlas, where configuration lives, and how to check that data is flowing.
esc
↑↓ navigate · ↵ open · ⌘K anywhere