Docs Portal
Documentation
API ReferenceConsole

Connector Entities

1. How Entities replaces place mappings

If you've configured connectors before, you'll remember place mappings: a hierarchical mapping table where you matched source-system rows to a Site → Building → Floor → Space → Thing tree, with a separate "provision" step and template-based mapping rules. Entities replaces all of that.

What changed, in customer terms:

Old way (place mappings)New way (Entities)
A nested Site→Building→Floor→Space tree you filled inA flat table (Grid), one screen per kind of thing
Separate "provision" step, then "map" stepOne screen, one flow — Import, then Save
Mapping a Space to a Floor was a separate column/targetThe Space simply has a Floor cell you pick
Rules written as text templatesRules written as a small TypeScript function (optional)

One screen per "source type." A connector groups its data into source types — the names the source system uses for its own things (for example Airthings exposes building, space, and thing). Each source type gets its own section in the connector's settings, and each section is its own Entities screen with its own Grid and Rules. So a customer might see a Buildings screen, a Spaces screen, and a Devices screen, and work through them one at a time.

During a migration you may see both. A connector that's being moved from the old model to Entities can temporarily show the old mapping table and a red "Entities Migration" section. Migration is a one-time rename that keeps all existing data and history intact. Documentation on migration to be released soon.

What one Entities screen looks like

Two tabs at the top:

  • Grid — the table of entities (this is where most work happens).
  • Rules — the optional merge-rules editor

Two buttons at the top-right:

  • Import — pull the current list from the source system.
  • Save — write your choices into Mapped.

Above the Grid there's a smart search bar which can narrow what an Import pulls in. Depending on the connector, there may be filter fields (for example a Site picker or a sensor-type selector). Some connectors have no filters. Previous filter selections are saved between imports.

An empty Grid at the bottom reads "No entities found. Click Import to load entities from the connector."

2. What happens when you click Import

Import fetches the current, live list of entities from the source system and lays them out as rows in the Grid. Each row is one entity — one building, one room, one sensor.

On import, for each row the connector provides:

  • A name and other typed values (floor level, space code, address, which floor a room is on, …). These become the cells you can edit.
  • Read-only reference columns on the right (greyed, italic) — internal IDs and any extra context the connector attached. These are for lookup/troubleshooting; you can't edit them, but you can copy any cell by hovering it and clicking the copy button that appears.
  • A suggested default value for each editable cell (for example the name as the source system spells it).

Nothing is written to Mapped yet. Import only loads and showsSave is what commits.

Import reflects the source system exactly. The list you get is the complete current state of that source type. If something exists in the source, it shows up; if it doesn't, it won't. That matters a lot for re-imports — see the next section.

3. Imports across time — what changes are shown

Import isn't a one-time action. A customer can click Import again whenever the source system changes (new rooms added, a sensor decommissioned, a name corrected). When they do, the screen compares the fresh import against what's currently saved in Mapped and highlights the differences.

You'll see a change banner / count summarizing the delta, using three categories:

  • Added — entities in the new import that weren't saved before (brand-new rows).
  • Updated — entities that already existed but whose values changed.
  • Deleted — entities that were saved before but are no longer reported by the source system.

Rows the source stopped reporting

An entity that the source system no longer returns is shown struck through (a line through the whole row) in red, its checkbox unticked and disabled, and its cells read-only. The next Save will remove it from Mapped.

These "gone" rows can't be kept. If the source system dropped an entity, nobody — not the customer, not a merge rule — can re-select it to keep it. The only way to keep it is for it to reappear in the source system and be imported again. This is deliberate: Mapped mirrors the source.

Your manual edits survive re-imports

Good news for customers: manual edits they made to a cell are remembered across re-imports. If a customer renamed a room and saved it, a later Import won't silently overwrite that name back to the source's version. (Two per-field flags — whether a cell is required and whether it must be unique — are refreshed on every import, because those are controlled by the connector, not the customer.)

Counts of any changes made sit at the bottom-left of the Grid:

Count indicatorColor
12 SELECTEDblue
3 ADDEDgreen
1 UPDATEDamber
2 DELETEDred

Only counts above zero appear. The bottom-right shows how much is loaded, e.g. 200 of 1450. Click any of these counts to filter the Grid down to those rows. For example, clicking 2 Deleted filters the Grid down to exactly those two rows. It is the fastest way to answer "what is about to be deleted?" The same counts appear in the Review Changes dialog, and clicking one there closes the dialog and applies the filter.

4. What happens when you edit cells

The Grid is editable. A customer can double-click into most cells and change the value — rename a space, set a floor level, pick which floor a room belongs to, and so on. Each column uses an editor suited to its type (a text box for a name, a number for a floor level, a searchable picker for "which floor").

The "which floor / which building" picker (entity references)

Some cells don't hold plain text — they hold a link to another entity. For example, a Space has a "Floor" cell that points at the Floor it sits on. Editing that cell opens a tree picker of eligible places, and the customer selects one.

  • The picker only lets you choose valid targets for that link (a Floor cell only lists floors).
  • For large buildings, the picker shows the first chunk of results and says "Showing 500 of N — search to narrow." Keep scrolling to view more, or use the search bar to find specific records.

Cell colour tells you where a value came from

This is the single most useful thing to teach a customer. Every cell is color-coded by its source:

What you seeWhat it means
Amber borderUnsaved edit — save to apply.
Dark-blue border, read-onlySet by a rule. Locked to manual edits.
Greyed textDefault value (placeholder)
No special stylingValue from the Graph.
Red borderDuplicate value in a unique field — must be unique to save.

There's a legend behind the little help icon on the checkbox column header that explains these colors. If two apply at once, red wins over blue, blue wins over amber, amber wins over grey.

Required and unique fields

  • Required fields are marked with an asterisk in the column header. If a required cell is empty, that row's checkbox is disabled — you can't include the entity until the field has a value. Hovering the checkbox shows which fields are missing.
  • Unique fields must not repeat across rows. If two rows share the same value in a unique column, both offending cells turn red and their checkboxes are disabled until the customer fixes one. (Blank values never count as duplicates.)

The checkbox = include or remove

The first column of every row is a checkbox:

  • Ticked = include this entity in Mapped (it will be created/updated on Save).
  • Unticking a row that's already saved = remove that entity from Mapped on Save.
  • A checkbox can be disabled because a required field is empty, a unique-field conflict exists, the source stopped reporting the row, or a merge rule controls the selection (see next document).

5. What happens when you click Save

Save is the commit. It takes everything the customer has selected and edited and writes it to Mapped in one action.

The "Review Changes" confirmation

Clicking Save opens a Review Changes dialog before anything is written. It shows:

  • A "What are the changes for?" note field — a short reason the customer can type, for their own audit trail. The description is required to submit the change.
  • A summary of the change counts (added / updated / deleted).
  • If the customer only edited merge rules and touched no rows, it says "No entity changes — this saves your merge rule only."

The customer clicks Submit to proceed (or Cancel to back out).

What Save actually does

On Submit, Mapped processes the whole batch at once:

  • Selected (ticked) rows are created or updated in the graph, with the values shown in their cells.
  • Removed rows (unticked, or struck-through because the source dropped them) are deleted from the graph.

After a successful Save, the freshly edited cells become "settled" (no more amber borders), and those entities are now live in Mapped — visible to the rest of the platform.

If a Save fails, the Save button turns amber and permanently displays: "Latest merge failed. Click here to try again." Clicking retries straight away, and no changes are lost.

Save is per-screen. Each source type (Buildings, Spaces, Devices…) has its own Import and its own Save. A customer works through and saves each screen.

Quick reference — the whole flow

1 . Open the connector → open a source-type section (e.g. Spaces).

2 . (Optional) set any filters above the Grid.

3 . Click Import → rows load into the Grid.

4 . Review the change counts if this isn't the first import.

5 . Tick the rows to include; edit cells as needed (watch the colours).

6 . (Optional) use the Rules tab to auto-fill — see Merge Rules

7 . Click SaveReview ChangesSubmit.

8 . Repeat for the next source-type section.

Merge Rules

Merge rules are the advanced configuration options for importing data into the Mapped platform from a connector. They let you automate what would otherwise be manual row-by-row work in the Grid: instead of ticking and editing hundreds of entities by hand, you write a small piece of logic that runs once for every imported entity and fills in values and/or ticks checkboxes automatically.

This document teaches you to read an existing rule and write your own. It's written so both Customer Success and customers can use it — merge rules are a supported, customer-facing part of configuring a connector, not an internal-only tool.

Merge rules are written in code (TypeScript). That's what makes them "advanced." You don't need to be a developer, but you do need to be comfortable reading and editing a short function. The Rules editor helps you: it autocompletes, checks your syntax as you type, and shows you exactly which fields exist for the connector you're configuring.


1. What's a merge rule?

A merge rule is a single function that the platform runs against one entity at a time, for every row in the Grid, every time you Import or preview. For each entity, the rule can:

  • Read facts about that entity — its source-system ID, extra context the connector attached, its current field values, and fixed identifiers (organisation, connector).
  • Set typed field values on it — force the name, set a floor level, point it at a floor.
  • Set its selection — tick or untick the checkbox to include or exclude it.

Whatever the rule sets shows up in the Grid as rule-owned cells (dark-blue border, read-only) and, where the rule sets selection, a rule-locked checkbox (disabled). Everything the rule doesn't touch is left to the customer's manual edits and the source defaults.

Why use them? Common jobs:

  • "Automatically include every room whose code starts with L (real leasable space) and skip the rest."
  • "Set every device's name from its serial number when the source didn't provide one."
  • "Attach every sensor with floor context 12 to our Floor 12 entity."

A rule applies one decision across thousands of rows, consistently, and re-applies automatically on every future import. A rule can point one entity at another by giving one of three things: the target's mapping key, its Mapped id, or its identity. For example:

Copy
1
entity.setField('floor', { type: 'FLOOR', mappingKey: 'msrc://…' })

If the field only accepts certain kinds of target (a Floor cell only accepts floors), it rejects anything else as you type — the same restriction the Grid picker enforces.

2. Where rules live — the Rules tab

Each Entities screen has a Rules tab next to the Grid tab. It's a code editor (Monaco — the same engine as VS Code) with:

  • A rules.ts tab — the editable rule.
  • A read-only types.d.ts tab — an auto-generated reference showing exactly which fields and context this connector's entities have. This is your cheat sheet: it gives you autocomplete and red underlines for typos, and it's the definitive answer to "what can I read and set here?"
  • A status bar at the bottom that reads "TypeScript valid." when the rule is well-formed, or shows the first error. A rule with errors shouldn't be relied on, but they can be saved so you save your work.
  • Format (tidy the code) and Reset (return to the starter template) buttons.

The editor opens on this starter template, which does nothing until you fill it in:

Copy
1
2
3
4
5
6
7
// Merge rules — runs once per imported entity.
export default function rules(entity: RulesEntity): RulesEntity {
  // Example:
  //   entity.setField('name', 'Renamed')
  //   entity.setSelected(true)
  return entity
}

Always check the types.d.ts tab first. It's generated from the real entity for that source type, so it lists the actual field slugs (name, floorLevel, floor, …) and context keys the connector emits. Don't guess names — read them off that tab.


3. How to read a rule

A rule is just a function with a body. Read it as a list of "for this entity, do X." The building blocks are a handful of entity.* calls, split into reads and writes.

Reads — what a rule can look at

CallWhat it gives you
entity.getRefId()The entity's ID in the source system.
entity.getContext('key')Extra data the connector attached (e.g. a wing, a floor number, a status flag). Keys are listed in types.d.ts.
entity.getField('name')The entity's current value for a field (default, or a prior manual edit).
entity.getMappedId()The entity's existing Mapped ID, if it's already saved.
entity.getOrgId() / entity.getConnectorId() / entity.getConnectorTypeId()Fixed identifiers for the org and connector — occasionally useful for conditions.

Review the types.d.ts tab for a full list of read calls.

Writes — what a rule can change

CallWhat it does
entity.setField('slug', value)Sets a typed field value. The cell becomes rule-owned (dark-blue, read-only).
entity.setSelected(true | false)Ticks (include) or unticks (exclude) the checkbox. The checkbox becomes rule-locked.

A rule always returns the entity at the end (return entity) so the platform can apply the changes.

Worked example — reading it line by line

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
export default function rules(entity: RulesEntity): RulesEntity {
  // 1. Only include "real" leasable rooms — skip service closets, risers, etc.
  const code = entity.getField('spaceCode')          // read the room code, e.g. "L12-204"
  const isLeasable = typeof code === 'string' && code.startsWith('L')
  entity.setSelected(isLeasable)                       // tick it only if it's leasable

  // 2. If the source didn't give this room a name, fall back to its code.
  if (!entity.getField('name') && code) {
    entity.setField('name', code)                      // set the name to the code
  }

  return entity                                        // hand the entity back
}

Read as: "Tick every room whose code starts with L; for any of those missing a name, use its code as the name; leave everything else alone." That single rule then applies to every row on every import.

4. How to write a rule

The workflow:

  1. Open the source-type section (e.g. Spaces) and click Import at least once, so there are entities to work against and the reference types are generated.
  2. Go to the Rules tab and open types.d.ts to see the available fields and context keys.
  3. In rules.ts, write your logic using the reads/writes above. Start small — a single setSelected is a complete, useful rule.
  4. Watch the status bar: it should say "TypeScript valid." Fix any underlined errors.
  5. Switch back to the Grid to preview the effect — rule-set cells turn dark-blue and rule-locked checkboxes become disabled.
  6. Click Save. The rule is saved and will re-run on every future import.

Patterns customers use most

Include or exclude by a condition:

Copy
1
2
3
4
5
export default function rules(entity: RulesEntity): RulesEntity {
  // Include only occupancy sensors; leave everything else unticked.
  entity.setSelected(entity.getContext('deviceKind') === 'occupancy')
  return entity
}

Fill a missing value:

Copy
1
2
3
4
5
6
7
export default function rules(entity: RulesEntity): RulesEntity {
  // Give unnamed devices a name based on their source ID.
  if (!entity.getField('name')) {
    entity.setField('name', **Device ${entity.getRefId()}**)
  }
  return entity
}

Combine reads:

Copy
1
2
3
4
5
6
7
8
9
10
11
export default function rules(entity: RulesEntity): RulesEntity {
  const floor = entity.getContext('floorNumber')
  // Only bring in entities that report a floor, and stamp it into the name.
  if (floor != null) {
    entity.setField('name', **${entity.getField('name')} (Floor ${floor})**)
    entity.setSelected(true)
  } else {
    entity.setSelected(false)
  }
  return entity
}

Good habits

  • Only touch what you mean to. Anything the rule doesn't set stays under the customer's control. A rule that only calls setSelected never disturbs any names.
  • Prefer clear conditions over cleverness. Rules are read by other people (including future you and CS). Simple if checks on context/fields are ideal.
  • Let the reference types guide you. If types.d.ts doesn't list a field or context key, it isn't available — don't invent one.
  • Rules run every time the Grid loads or refreshes or Saves, so they keep decisions consistent as the source changes. A new room that matches the rule is handled automatically next time.

5. How manual edits and rules interact

This is the question customers ask most, so it gets its own section.

Every cell has a winning value, chosen from up to four sources in this priority order (highest wins):

1 . A merge rule — wins outright, locks the cell.

2 . An edit made in this session — typed but not saved yet.

3 . An edit saved earlier — survives re-imports.

4 . The value currently live in Mappedmissing from the list above

5 . The source system's default.

In plain language:

  • A rule overrides manual edits for the fields it touches. If a rule sets name, you can't hand-edit the name while that rule is in place — the cell is locked. To edit it by hand, change the rule so it stops setting that field.
  • A rule only owns the fields it sets. Every other cell still respects manual edits. A rule that sets only floor leaves name fully editable.
  • Manual edits beat defaults. For any field no rule controls, your typed value wins over the source default — and survives re-imports.
  • Selection works the same way. If a rule calls setSelected, the checkbox is rule-locked and you can't click to override it. If the rule doesn't touch selection, you control the checkbox normally.

Two hard gates that override even a rule

These protect data integrity and win over everything, including a rule:

  • Required-but-empty — a rule can't include an entity that's missing a required value.
  • Unique-field conflict (red cell) — blocks selection until the duplicate is resolved.

Rule of thumb: rules are for decisions you want made the same way every time; manual edits are for one-off corrections. If a cell is locked dark-blue, a rule owns it — change the rule, not the cell.

6. What Save does with a rule

  • The rule itself is saved alongside the merge, so it persists and re-runs on future imports.
  • If you changed only the rule (no rows), the Review Changes dialog shows "No entity changes — this saves your merge rule only." Save still works; it just stores the rule.
  • If you changed rows and the rule, Save does both.

So you can iterate: tweak the rule → SaveImport later → the rule re-runs automatically.

Example: Setting Building Names and Postal Addresses with Grid Rules

If you maintain a reference of building identifiers, building names, and postal addresses, you can use a Grid Rule to automatically populate those values during import.

The rule matches each building using its source-system Ref ID, then applies the correct name and postal address automatically.

Using the building Ref ID is recommended because it is generally stable even if the building name changes over time.

Example Spreadsheet

Building Ref IDCanonical Building NamePostal Address
<building-id-1>Example Campus Building A100 Main Street, Springfield, MA 01103
<building-id-2>Example Campus Building B200 Main Street, Springfield, MA 01103
<building-id-3>Example Campus Building C300 Main Street, Springfield, MA 01103

Create the Lookup Table

Create an object that maps each building Ref ID to its canonical name and postal address.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const BUILDINGS: Record<
  string,
  {
    name: string
    postalAddress: string
  }
> = {
  '<building-id-1>': {
    name: 'Example Campus Building A',
    postalAddress: '100 Main Street, Springfield, MA 01103',
  },

  '<building-id-2>': {
    name: 'Example Campus Building B',
    postalAddress: '200 Main Street, Springfield, MA 01103',
  },

  '<building-id-3>': {
    name: 'Example Campus Building C',
    postalAddress: '300 Main Street, Springfield, MA 01103',
  },
}

Apply the Values

The rule looks up the imported building by Ref ID. If a matching entry exists, it applies the canonical name and postal address and selects the building.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default function rules(entity: RulesEntity): RulesEntity {
  const building = BUILDINGS[entity.getRefId()]

  // Unknown buildings are left untouched so they appear in the
  // Grid for review instead of being assigned incorrect values.
  if (!building) {
    return entity
  }

  entity.setField('name', building.name)
  entity.setField('postalAddress', building.postalAddress)

  entity.setSelected(true)

  return entity
}

Put it together

Replace the example Ref IDs, building names, and addresses with values for your buildings.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const BUILDINGS = {
  'building-ref-id-1': {
    name: 'Example Campus Building A',
    postalAddress: '100 Main Street, Springfield, MA 01103',
  },

  'building-ref-id-2': {
    name: 'Example Campus Building B',
    postalAddress: '200 Main Street, Springfield, MA 01103',
  },

  'building-ref-id-3': {
    name: 'Example Campus Building C',
    postalAddress: '300 Main Street, Springfield, MA 01103',
  },
}

export default function rules(entity: RulesEntity): RulesEntity {
  const building = BUILDINGS[entity.getRefId()]

  // If this building is not in the lookup table,
  // leave it unchanged so it can be reviewed.
  if (!building) {
    return entity
  }

  entity.setField('name', building.name)
  entity.setField('postalAddress', building.postalAddress)

  entity.setSelected(true)

  return entity
}

Why This Pattern Is Recommended

  • Ref IDs are typically more stable than names.
  • Names can be standardized independently of the source system.
  • Postal addresses can be maintained centrally.
  • New buildings remain visible for review instead of receiving guessed values.
  • The same rule automatically applies on every future import.

Rules FAQ

Do I have to use rules? No. They're optional advanced configuration. Connectors can be set up entirely by ticking and editing in the Grid.

Can a rule delete entities? A rule can untick a row (setSelected(false)), which un-selects an already-saved entity on the next Save. Items that are un-selected and then saved are intended to be removed from the Mapped Graph, but there is currently no capability to delete entities from the connector runtime. There's work for that to be a possibility in the future. In the meantime, the entity may become an orphan in the Graph.

What if the rule has an error? The editor shows it in the status bar and underlines it in the code. It can be saved so that you save your work.

What kind of errors can happen with rules? There are two different kinds of "error", and only one shows up in the Rules editor. First, the editor may complain (red underline, orange status bar), which indicates the rule doesn't quite typecheck. It still saves and still runs.

Second, the rule may crash while running, and this is indicated in the Grid, not the editor. In this case the rule does not apply. On every affected row, a red icon replaces the checkbox, and the row can't be selected or deselected until the rule is fixed. Hovering it says whether the rule failed on that one row or on every row.

Where do I see what fields I can use? The read-only types.d.ts tab in the Rules editor — it's generated per connector and always current.