Object Registry - a named object store for Home Assistant automations and scripts

Hi all. I'm excited- and nervous- to make my first post here in Share Your Project.

I built a small HACS integration called Object Registry. The basic idea is simple: store named, structured objects in Home Assistant, manage them through a sidebar UI, and use them to cleanly separate config from logic in automations or scripts. (Perhaps just the habit of the rusty programmer nerd in me.) If you've ever spent a week fiddling with hundreds of lines of custom YAML script, then broke it because you tried to tweak one small mapping between objects or states, this may be for you.

JSON is the storage and editing format for objects, but automations and scripts interact with the returned data as normal object data. I chose JSON since that since that's the HA native Store interface behind the scenes for custom_integrations.

The problem that pushed me here was ISY/Insteon remotes controlling Hue lights, then expanded to fans, sprinkler zones, temperature sensors, and more. Sensors and devices raising events that should have many different outcomes depending on context. I recently switched to HA after 15 years on ISY, and this is the sort of automation that I'd always wanted to do. And wow, has HA quickly converted years of one day, maybe, into done, what's next.

But after a while I'd built a catalog of complex automations with the mapping data hardcoded into YAML: button sensors, control codes, target lights, brightness settings, color values, dimming actions, iteration step settings, action targets, and so on. That works until you add another remote, change a scene, or want the same automation pattern across multiple rooms. Then the automation starts becoming both logic and configuration.

Object Registry separates those two things.

The automation stays generic. The registry object holds the mapping.

For example, an automation can receive an ISY control event, look up the source button in Object Registry, resolve the target light and action, and execute it. If I want to remap a button or change the data passed to a service call, I edit the registry object in the UI instead of editing automation YAML.

The integration provides three services to HA:

  • object_registry.get_data - returns the stored data for an object
  • object_registry.get_object - returns the full object with metadata
  • object_registry.list_items - returns metadata for all objects

Objects are persisted using Home Assistant's native storage interface. They are managed through a custom sidebar panel, so normal use does not require file editing or restarts. The panel subscribes to hass events and automatically updates list items in real time if you have multiple people editing. If you're in object edit mode, you'll get a banner letting you know if the background object has been changed by someone else, and uses the familiar validating HA editor to make things easy.

The repo includes the ISY remote routing example that started this, plus scripting notes from the Home Assistant YAML gotchas I ran into while making dynamic service calls from registry data.

GitHub:

The core of my home automation approach came out of trying to keep the house "normal" for my family in the best possible way: switches should still feel like switches, remotes should still feel like remotes, and the complexity should stay behind the curtain. They don't want to dig out their phones every time they need to control something at the end of a long day.

I built the integration with a 2026 level of AI help as documented in the repo, a lot of testing in a my live HA setup, and loads of inspiration from all of your excellent documentation and forum posts. Thank you!!

Feedback welcome, especially if this pattern fits a use case I have not thought of yet.

2 Likes

I like the concept.

Ideally, the integration would offer an action to write to the Object Registry.

I see the practicality, of using the Object Registry to separate data from logic, if multiple automations/scripts need to reference the same data; it prevents data duplication.

However, I am unconvinced of its utility if just one automation/script requires the data. In that case, my preference would be to keep the data within the sole automation/script that needs it. To separate data from logic, it can be stored in the automation's variables.

Based on this screenshot, it appears all of the JSON is composed manually in the editor (without any automated assistance from the editor).

Personally, I don't find writing data structures in JSON to be easier than YAML.

For example, the YAML equivalent of this hairy bit of JSON:

{
  "sensor.main_left_remote_a_b": {
    "target": "light.main_bedroom",
    "controls": {
      "DON": {
        "action": "light.turn_on"
      },
      "DOF": {
        "action": "light.turn_off"
      },
      "DFON": {
        "action": "light.turn_on",
        "data": {
          "brightness_pct": 100,
          "xy_color": [
            0.422,
            0.399
          ],
          "transition": 0.1
        }
      }
    }
  }
}

is this:

sensor.main_left_remote_a_b:
  target: light.main_bedroom
  controls:
    DON:
      action: light.turn_on
    DOF:
      action: light.turn_off
    DFON:
      action: light.turn_on
      data:
        brightness_pct: 100
        xy_color:
          - 0.422
          - 0.399
        transition: 0.1

It's arguably simpler to compose because it doesn't need braces, double-quotes, or commas (just correct indentation).

All this to say that an automation can be structured like this where the logic is at the top, the data at the bottom, and the data is easy to modify.

alias: example
triggers:
  ...
conditions:
  ...
actions:
  ...
variables:
  registry:
    sensor.main_left_remote_a_b:
      target: light.main_bedroom
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1

Where several automations need access to shared data, I think your Object Registry fulfills the requirement very well. However, if it's just one automation then, in my opinion, it's easily done with variables alone.


Whether you agree with my opinion or not, I offer you two simplified versions of your automation example.

The first version uses the Object Registry but its logic has been simplified.

Simplified version using Object Registry
alias: ISY Insteon Remote Router
description: >
  Routes ISY remote button events to HA actions via the Object Registry. All
  mapping config lives in the isy_button_map registry object.
triggers:
  - trigger: event
    event_type: isy994_control
    variables:
      source_entity: "{{ trigger.event.data.entity_id | default('') }}"
      control: "{{ trigger.event.data.control | default('') }}"
      source_labels: "{{ labels(source_entity) if source_entity else [] }}"
conditions:
  - condition: template
    value_template: |-
      {{ source_entity != ''
         and control != ''
         and 'isy_insteon_remote_button' in source_labels }}
actions:
  - action: object_registry.get_data
    data:
      object_id: isy_button_map
    response_variable: button_map
  - variables:
      mapping: "{{ button_map.get('data', {}) }}"
      remote_config: "{{ mapping.get(source_entity, {}) }}"
      target_entity: "{{ remote_config.get('target', '') }}"
      control_config: "{{ remote_config.get('controls', {}).get(control, {}) }}"
      mapped_action: "{{ control_config.get('action', '') }}"
      action_data: "{{ control_config.get('data', {}) }}"
  - if:
      - condition: template
        value_template: "{{ mapped_action != '' and target_entity != '' }}"
    then:
      - action: "{{ mapped_action }}"
        target:
          entity_id: "{{ target_entity }}"
        data: "{{ action_data }}"
    else:
      - action: persistent_notification.create
        data:
          title: ISY Router Unhandled
          message: >-
            No mapping found for control {{ control }} from {{
            source_entity }}.
mode: queued
max: 20

The second version uses variables and its logic is even simpler, largely because it doesn't need to retrieve data with an action.

Simplified version using variables
alias: ISY Insteon Remote Router
description: >
  Routes ISY remote button events to HA actions via variables.
triggers:
  - trigger: event
    event_type: isy994_control
    variables:
      source_entity: "{{ trigger.event.data.entity_id | default('') }}"
      control: "{{ trigger.event.data.control | default('') }}"
      source_labels: "{{ labels(source_entity) if source_entity else [] }}"
conditions:
  - condition: template
    value_template: |-
      {{ source_entity != ''
         and control != ''
         and 'isy_insteon_remote_button' in source_labels }}
actions:
  - variables:
      remote_config: "{{ registry.get(source_entity, {}) }}"
      target_entity: "{{ remote_config.get('target', '') }}"
      control_config: "{{ remote_config.get('controls', {}).get(control, {}) }}"
      mapped_action: "{{ control_config.get('action', '') }}"
      action_data: "{{ control_config.get('data', {}) }}"
  - if:
      - condition: template
        value_template: "{{ mapped_action != '' and target_entity != '' }}"
    then:
      - action: "{{ mapped_action }}"
        target:
          entity_id: "{{ target_entity }}"
        data: "{{ action_data }}"
    else:
      - action: persistent_notification.create
        data:
          title: ISY Router Unhandled
          message: >-
            No mapping found for control {{ control }} from {{
            source_entity }}.
mode: queued
max: 20
variables:
  registry:
    sensor.main_left_remote_a_b:
      target: light.main_bedroom
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
        DFOF:
          action: light.turn_on
          data:
            color_temp_kelvin: 2710
            brightness: 122
            transition: 2
        FDUP:
          action: hue_dimmer.raise
          data:
            sweep_time: 8
        FDDOWN:
          action: hue_dimmer.lower
          data:
            sweep_time: 8
        FDSTOP:
          action: hue_dimmer.stop
    sensor.main_right_remote_a_b:
      target: light.main_bedroom
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
        DFOF:
          action: light.turn_on
          data:
            color_temp_kelvin: 2710
            brightness: 122
            transition: 2
        FDUP:
          action: hue_dimmer.raise
          data:
            sweep_time: 8
        FDDOWN:
          action: hue_dimmer.lower
          data:
            sweep_time: 8
        FDSTOP:
          action: hue_dimmer.stop
    sensor.main_bedroom_8btn_switch_c:
      target: light.main_bedroom
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
        DFOF:
          action: light.turn_on
          data:
            color_temp_kelvin: 2710
            brightness: 122
            transition: 2
        FDUP:
          action: hue_dimmer.raise
          data:
            sweep_time: 8
        FDDOWN:
          action: hue_dimmer.lower
          data:
            sweep_time: 8
        FDSTOP:
          action: hue_dimmer.stop
    sensor.office_remote_b:
      target: light.office
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
        DFOF:
          action: light.turn_on
          data:
            color_temp_kelvin: 2710
            brightness: 122
            transition: 2
        FDUP:
          action: hue_dimmer.raise
          data:
            sweep_time: 8
        FDDOWN:
          action: hue_dimmer.lower
          data:
            sweep_time: 8
        FDSTOP:
          action: hue_dimmer.stop
    sensor.guest_remote_a:
      target: light.guest_bedside
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
        DFOF:
          action: light.turn_on
          data:
            color_temp_kelvin: 2710
            brightness: 122
            transition: 2
        FDUP:
          action: hue_dimmer.raise
          data:
            sweep_time: 8
        FDDOWN:
          action: hue_dimmer.lower
          data:
            sweep_time: 8
        FDSTOP:
          action: hue_dimmer.stop

Naturally this version's overall length is greater because variables contains all of the data (in YAML) that would have been stored in the Object Registry (in JSON).


FWIW, I tested a modified version of the second example on my system and it worked well. I used Developer tools -> Events to simulate isy994_control events and was able to control a light's state and brightness via different control values.

I normally do this using a long-winded choose, with the actions and targets "hard-coded" in its choices, but I like this logic/data approach. In terms of legibility, instead of scrolling through a lengthy choose, I get a compact summary of all controlling events and associated actions for a given light.

      target: light.main_bedroom
      controls:
        DON:
          action: light.turn_on
        DOF:
          action: light.turn_off
        DFON:
          action: light.turn_on
          data:
            brightness_pct: 100
            xy_color:
              - 0.422
              - 0.399
            transition: 0.1
1 Like

Thanks for your thoughtful feedback, @123. Especially for installing it and testing. I need to add a better set of examples that aren't ISY-specific. Indeed, 1:1 map to script is not the use case — it's for heavy reuse of common configs across multiple automations and scripts. As you noted, 1:1 variables, as with env vars, is tidy.

I expected requests related to extending the script contract with add/update, and appreciate your confirmation. Yes, YAML is tighter, and the editor supports it. I'd considered an intermediate conversion inline, or stuffing serialized YAML into Store calls, but both have smells. My real vision is to offer several data types, including YAML, XML, and binary, at which point I'd just roll my own file-based store under the integration folder.

Also, I'm likely to move away from my Frankenstein HTML panel, and roll a proper Lit UI if there's interest. That felt it might require Lovelace debugging experience out of scope for V1.

Cheers!

Will your registry cope with the constant changes the developers make to HomeAssistant, step for step?

@IOT7712 that is a good question, and part of this experiment. I've specifically tried to decouple as much as possible, while relying on the core in the most stable way I can.

For example, using an HTML panel rather than Lit/Lovelace heavy design, makes the panel code bigger and more HTML/JS/CSS for me to maintain from a visual styling perspective, but also more decoupled from HA Lovelace specific contracts. Changes the hass websockets would be the largest issue, but braking changes in that API will break everyone else too. Store likewise seems to be pretty stable as well as the method registration code for access by automations and scripts.

And to your point, I chose that to be as stable as possible, as set-by-step co-release of any tool, platform, integration, API etc isn't guaranteed for any developer regardless of size. At least not without a formal tech partner program with NDA's for roadmap disclosure, early testing etc. And HA itself is a community platform, and those aren't options anyway.

So, I'll do my best, but one reason it's licensed under MIT is I can't provide a warranty of compliance or upstream breaking change fix SLA. I believe that's common across the bulk of HACS projects as well. Most integrations are maintained by passionate individuals rather than professional teams. I'm motivated by a desire to assist the community, which I believe is aligned with an underlying intent to adapt to unexpected breaking changes as quickly as possible.

Are there specific patterns or dependancies in my project of particular concern?