I have two Z-Wave deadbolts on the house — one on the front door, one on the door from the garage into the laundry room. Both run PIN code management through Keymaster, the long-standing HACS integration for Z-Wave lock code slot management. They’ve been running like that for over a year. They mostly work.
But if I were starting over today, I’d use Lock Code Manager instead, and the Keymaster install I have now is on borrowed time. This post is the comparison, the actual notification automation I run, and the lessons from the most painful Keymaster bug I’ve hit.
What lock code management actually means
Both of my deadbolts are Z-Wave with built-in keypads. They store up to ~30 PIN codes locally on the lock itself; you punch in a code, the lock opens. The codes are stored in numbered “slots.” Slot 1 might be my code, slot 2 my wife’s, slot 3 the dog walker’s, and so on.
Out of the box, Home Assistant’s Z-Wave JS integration only exposes the lock as a binary entity (lock.frontdoor — locked or unlocked) and a few diagnostic sensors. It does not give you a friendly way to manage which PINs are in which slots, restrict slots to certain dates and times, or get a notification telling you who just unlocked the door.
That’s the gap Keymaster (and Lock Code Manager) fills. They sit on top of Z-Wave JS and add:
- A slot-management UI (assign a name and a PIN to slot 1, 2, 3, etc.)
- Time/day restrictions per slot (“dog walker’s PIN only works Mon-Fri 9-11 AM”)
- Slot-aware unlock events (so you can see which slot was used)
- Auto-generated Lovelace dashboards for managing all of this
Useful for a four-person household with periodic guests, dog walkers, cleaners, contractors. Not strictly necessary if you only have two PINs and never change them.
The two integrations
There are essentially two HACS integrations doing this job in 2026:
Keymaster (FutureTense/keymaster) — the older, more entrenched option. Has been the de-facto answer in HA forums for years. Mature, lots of community guides, well-known.
Lock Code Manager (raman325/lock_code_manager) — the newer, cleaner alternative. Designed explicitly as a modern replacement, supports Z-Wave + Matter + a wider lock list (including Schlage models that Keymaster historically struggled with), produces fewer entities per slot, and has lighter-weight automation requirements.
I’m running Keymaster because it’s what I installed in 2024 when the choice was effectively just Keymaster. If I were starting clean today, the choice is obvious — but I’m not, and migrating is non-trivial because the entity names change.
Keymaster vs Lock Code Manager: side-by-side
Here’s the honest head-to-head. The Keymaster column reflects what I actually run; the Lock Code Manager column is drawn from its project documentation, not personal use.
| Keymaster | Lock Code Manager | |
|---|---|---|
| Repository | FutureTense/keymaster | raman325/lock_code_manager |
| Install | HACS custom integration | HACS custom integration |
| Config storage | UI + generated packages/ YAML | UI / config entry only, no packages |
| Protocols | Z-Wave JS, ZHA, Zigbee2MQTT, Schlage WiFi (Matter emerging) | Z-Wave JS, ZHA, Zigbee2MQTT, Matter |
| Entities per slot | Many — ~6 helpers/slot, 60+ on a 10-slot lock | Far fewer — a handful per slot, no input-helper pile |
| Automation model | Discrete input_* helpers you wire together | Service calls + first-party blueprints |
| Blueprints | Community-contributed | First-party: Auto Re-lock, Calendar PIN Setter, Slot Usage Limiter, Slot Usage Notifier, Lock on Door Close |
| PIN-usage tracking | Via alarm-level events + your own automations | Built-in per-slot usage sensors |
| Multi-lock sync | DIY automations (my copy-settings pattern) | Built-in slot sync with drift detection |
| Auto dashboard | Lovelace strategy dashboard | Dashboard strategy (HA 2026.5+) |
| Maturity | Older, very widely documented | Newer, actively developed |
| Best for | Existing setups; heavy per-slot helper automations | New setups; Matter locks; less entity clutter |
Which locks and protocols each one supports
The most common real question is “will it work with my lock?” Both are protocol-level tools — they talk to whatever lock your radio integration already exposes, so support comes down to which radios and lock types each one understands.
| Lock / protocol | Keymaster | Lock Code Manager |
|---|---|---|
| Z-Wave JS deadbolts (Schlage, Yale, Kwikset) | Yes | Yes |
| Zigbee via ZHA | Yes | Yes |
| Zigbee via Zigbee2MQTT | Yes | Yes |
| Matter locks | Emerging | Yes |
| Schlage WiFi (Encode) | Listed provider | Check current docs |
| Cloud/LAN locks | Akuvox supported via local_akuvox; Nuki/Tedee in progress | Check current docs |
Practical takeaway: for a mainstream Z-Wave or Zigbee keypad deadbolt, both work. For a Matter lock, Lock Code Manager is the safer pick today. WiFi and cloud locks (Schlage Encode, Nuki, Tedee) are a moving target on both — check the project’s current support table before you buy, since they depend on the underlying integration exposing code-slot control at all.
Before you start: prerequisites
Both integrations sit on top of an already-working lock. Before you install either one:
- Your lock is paired and controllable in Z-Wave JS (or ZHA / Zigbee2MQTT / Matter) — you should be able to lock and unlock it from the raw
lock.entity first. Neither add-on pairs the lock for you. - HACS is installed. Neither integration ships in core Home Assistant; both are HACS custom integrations.
- You’re on a current HA release. Keymaster’s slot flow changed with the 2025.8 Z-Wave JS updates (more below), and Lock Code Manager’s dashboard strategies need HA 2026.5 or newer. Older cores work; you just lose the auto-dashboard.
- (Keymaster only) a packages directory. Keymaster writes its generated helpers into a
packages/folder, soconfiguration.yamlneedshomeassistant: packages: !include_dir_named packagesfirst.
Installing Keymaster via HACS
If you do go with Keymaster, it installs through HACS (the Home Assistant Community Store): open HACS → Integrations, search for Keymaster, download it, then restart Home Assistant and add it from Settings → Devices & Services → Add Integration → Keymaster. Point it at your Z-Wave lock entity and set the number of PIN code slots you want to manage. Keymaster isn’t in the core integrations list, so you need HACS installed first.
Setting up Lock Code Manager
I don’t run Lock Code Manager, so treat this as the general procedure rather than a walkthrough of my box. Setup mirrors Keymaster’s, minus the YAML packages step:
- Install via HACS. In HACS, add
raman325/lock_code_manager(as a custom repository if it isn’t indexed yet), download it, and restart Home Assistant. - Add the integration. Go to Settings → Devices & Services → Add Integration → Lock Code Manager, then pick the lock entity (or entities — LCM can manage several locks under one config) and the number of code slots.
- Fill slots from the UI. Each slot gets a name, a PIN, and an enabled toggle. LCM pushes the code to the lock and reads it back to confirm, rather than leaving it in an “Adding” limbo state.
- Add blueprints as needed. LCM ships first-party blueprints — Auto Re-lock, Lock on Door Close, Calendar PIN Setter, Slot Usage Limiter, and Slot Usage Notifier — that you import from the repo and point at your slots. These replace the pile of hand-built helper automations Keymaster setups tend to accumulate.
Because LCM is config-entry and service driven, it doesn’t dump dozens of input_* helpers into your entity registry — its per-slot entities are namespaced, which is exactly the entity-sprawl fix I keep wishing Keymaster had.
Migrating from Keymaster to Lock Code Manager
I haven’t done this migration myself, so here’s the general procedure rather than a post-mortem. There’s no in-place converter — the two integrations name their entities differently, so it’s a clean cutover:
- Write down your slot map first. Record every active slot: number, name, PIN, and any time/day restriction. This is your source of truth for re-entry.
- Clear the codes off the lock. Use Keymaster to empty each slot so you don’t leave orphaned PINs in the deadbolt’s local storage. The lock only holds ~30 codes; stale ones cause confusion later.
- Remove Keymaster. Delete the Keymaster config entry from Settings → Devices & Services, then remove its generated
packages/file and restart. That clears theinput_text/input_datetime/input_booleansprawl. - Install and configure Lock Code Manager (steps above), recreating the same slot count.
- Re-enter every code from your slot map, including restrictions, and verify each one physically at the keypad before you rely on it.
- Rewrite automations for the new entity names. Keymaster’s
text.<lock>_code_slot_N_namebecomes LCM’stext.<lock>_lcm_slot_N_name; a find-and-replace ofcode_slot→lcm_slotcovers most templates. Anything driven offinput_datetime/input_booleanhelpers moves to LCM’s slot conditions or blueprints.
Budget an evening, do it when nobody needs door access, and keep one physical key on you in case a slot doesn’t take on the first push.
The Keymaster issues that are pushing me to migrate
Keymaster works for me most of the time. But I’ve hit three specific failure modes in the past year:
The 2025.8 update broke it. Z-Wave JS in HA 2025.8 made changes to how lock command-class events are surfaced, and Keymaster’s PIN-add flow got jammed: I’d add a PIN to a slot via the Lovelace dashboard, the slot would go into “Adding” status, and stay there indefinitely. The lock never received the new code. Recovery required manually removing the integration entry, restarting HA, re-adding it, and re-syncing every existing slot. Took an evening.
Disconnected state errors. Even after the 2025.8 issue settled, Keymaster periodically reports a Z-Wave node as “Disconnected” when the actual node is fine and Z-Wave JS is communicating with it normally. The fix is to delete and re-add the integration entry for the affected lock, but it shouldn’t need a fix — Keymaster’s connection state model and Z-Wave JS’s are slightly out of sync.
Entity sprawl. Each lock with 10 active slots produces 60+ entities — input_text per slot for PIN, input_text for name, input_datetime for start/end, input_boolean for enabled, etc. With two locks and 30 slots, the entity registry gets crowded. None of these entities have a keymaster_ prefix, so they’re scattered alphabetically with everything else and clutter the entity picker. Lock Code Manager produces dramatically fewer entities — a handful per slot — and is built around a slot-management service rather than a pile of input helpers.
The orphaned-lock recovery. Once, my front-door lock physically lost Z-Wave network association (battery died at the wrong moment, came back up not paired). Re-pairing it through Z-Wave JS worked, but Keymaster still had a config entry pointing to the old ghost device. Disabling and re-enabling the Keymaster config entry got it linked to the re-paired lock. Not fun, took half an hour to figure out.
None of these are dealbreakers individually. Together they’re enough to make me wish I’d started on Lock Code Manager.
Troubleshooting common Keymaster problems
A quick symptom-to-fix reference for the failures above, pulled out as scannable headers.
Slot stuck on “Adding” after HA 2025.8
Home Assistant 2025.8’s Z-Wave JS changes altered how lock command-class events surface, and Keymaster’s PIN-add can hang — the slot shows “Adding” forever and the code never reaches the lock. Fix: update both HA and Keymaster to their latest versions (later point releases smoothed this out), then remove the config entry, restart, re-add it, and re-sync each slot.
Node shows “Disconnected” but the lock is fine
Keymaster sometimes reports a node as Disconnected while Z-Wave JS talks to it normally — their connection-state models drift apart. Fix: delete and re-add the integration entry for that lock. If Z-Wave JS itself shows the node alive, it’s a Keymaster display issue, not a radio problem.
Lock re-paired but Keymaster points at the ghost device
If a lock drops off the network (dead battery, forced re-pair) and returns as a new device, Keymaster’s config entry still references the old one. Fix: disable and re-enable the Keymaster config entry to relink it.
What I run today: slot-aware unlock notifications
Whatever integration you use, the most useful automation in my Keymaster setup is “tell me who just unlocked the door, by name.” Z-Wave deadbolts emit an alarm_type=19 event when unlocked via keypad, with alarm_level equal to the slot number used. Keymaster lets you map slot numbers to names, so the automation becomes:
- id: 'lock_keypad_unlock_notify_charles'
alias: Lock Keypad Unlock Notification (Charles Only)
description: 'Notify Charles when any lock is unlocked via keypad with who did it'
triggers:
- entity_id: sensor.frontdoor_alarmtype
trigger: state
to: '19'
id: frontdoor
- entity_id: sensor.deadbolt_garagedoor_alarmtype
trigger: state
to: '19'
id: garagedoor
conditions: []
actions:
- variables:
lock_name: >-
{% if trigger.id == 'frontdoor' %}Front Door
{% else %}Garage Door{% endif %}
slot: >-
{% if trigger.id == 'frontdoor' %}
{{ states('sensor.frontdoor_alarmlevel') | int(0) }}
{% else %}
{{ states('sensor.deadbolt_garagedoor_alarmlevel') | int(0) }}
{% endif %}
slot_name_entity: >-
{% if trigger.id == 'frontdoor' %}
text.frontdoor_code_slot_{{ states('sensor.frontdoor_alarmlevel') | int(0) }}_name
{% else %}
text.garagedoor_code_slot_{{ states('sensor.deadbolt_garagedoor_alarmlevel') | int(0) }}_name
{% endif %}
person_name: >-
{{ states(slot_name_entity) if states(slot_name_entity) not in ['unknown', 'unavailable', ''] else 'Unknown (Slot ' ~ slot ~ ')' }}
- action: notify.charles
data:
title: "🔓 {{ lock_name }} Unlocked"
message: "{{ person_name }} unlocked the {{ lock_name | lower }} via keypad"
data:
tag: "lock-unlock-{{ trigger.id }}"
Walking through what this does:
- Fires on either lock’s
alarmtypesensor changing to 19 (Z-Wave alarm type for “keypad unlock”). Both triggers tagged withidso the action knows which lock. - Builds three variables: a friendly lock name, the slot number from the
alarmlevelsensor, and the entity ID of the slot’s name field —text.frontdoor_code_slot_4_name, for example. - Resolves the person name by looking up that entity. If the slot has no name set (state is unavailable / unknown / empty), falls back to “Unknown (Slot N)” so the notification still has useful info.
- Sends a tagged notification. The tag means a second unlock at the same door replaces the first notification rather than stacking.
End result: when the cleaner unlocks the front door, my phone says ”🔓 Front Door Unlocked: Cleaner unlocked the front door via keypad”. When the dog walker unlocks the garage, the notification says her name. If someone tries a slot that’s been emptied, I get “Unknown (Slot 7)” — useful as a security signal (someone tried a slot we don’t recognize).
This pattern works the same way under Lock Code Manager. The entity names are different (text.frontdoor_lcm_slot_4_name vs text.frontdoor_code_slot_4_name), but the alarm-type-19 event is the same Z-Wave-level signal regardless of which integration is reading it. If I migrate, I’ll just s/code_slot/lcm_slot/ in the template.
Cross-lock slot syncing
A small cute thing I have is automations that copy time-restriction settings between slots on the two locks. If I set the front-door slot 4 (cleaner) to start Wednesdays at 9 AM, an automation copies that start time to garage-door slot 4. Means I only edit settings once. Looks like:
- id: '1748887396910'
alias: Keymaster Copy Frontdoor Wed Start Date to Garagedoor
description: 'Sync Wednesday start date from frontdoor to garagedoor slot 4'
triggers:
- entity_id: input_datetime.wed_start_date_frontdoor_4
trigger: state
conditions:
- condition: state
entity_id: input_boolean.override_parent_garagedoor_4
state: 'off'
actions:
- target:
entity_id: input_datetime.wed_start_date_garagedoor_4
data:
time: '{{ states.input_datetime.wed_start_date_frontdoor_4.state }}'
action: input_datetime.set_datetime
The override_parent boolean is a per-slot override — if it’s on, the sync skips, and the garage-door slot keeps its independent value. Lets me hand the cleaner garage-only access during a one-off scheduling change.
This is the kind of thing that’s possible because Keymaster’s slots are exposed as discrete input_datetime / input_boolean / text helpers — you can wire them together with normal HA automations. Lock Code Manager has a more service-call-driven model, which is cleaner architecturally but means cross-slot syncing requires a slightly different pattern.
What I’d build now
Three pieces of advice based on a year of running this:
-
Use Lock Code Manager unless you have a specific reason not to. The 2025.8 breakage and the entity sprawl are real, and the maintenance trajectory of Lock Code Manager is better. The only reason to pick Keymaster in 2026 is if you have a lock model that LCM doesn’t support — and that list is short.
-
Don’t put a keypad-unlock notification behind a
for:debounce. I tried that early on, thinking it would prevent duplicate fires from a single press. It actually loses the first notification (the one you care about) on a quick unlock-then-relock; the alarm type goes back to its default before thefor:window expires. Use the notification tag instead — let HA fire the notification and let the OS dedupe. -
Keep PIN slots short and rotate them. A 4-digit PIN is fine for short-term access (cleaner, one-off contractor) but it’s a tiny keyspace — 10000 codes — and a determined attacker with physical access can brute-force it. Time-restricted slots help: a PIN that only works Tuesdays 9-11 AM has 99% less attack surface than a 24/7 PIN. Rotate any PINs given to outside service providers when the relationship ends.
What’s still missing for me
The piece I haven’t built yet, and probably should: a “lock didn’t lock” alarm. The lock has an auto-relock-after-5-minutes setting, but if for some reason the bolt doesn’t engage (warped door, cold weather affecting alignment), I want a notification rather than just trusting the lock’s silence. The Z-Wave lock entity does report whether the bolt is jammed, so it’s a one-automation job; I just haven’t done it.
Here’s the automation you’d write for it. Home Assistant lock entities report a jammed state when the bolt fails to throw, so a state trigger on jammed is all it takes:
- id: 'lock_jammed_alert'
alias: Lock Failed to Engage (Jammed) Alert
description: 'Notify when a deadbolt reports jammed instead of locked'
triggers:
- entity_id:
- lock.frontdoor
- lock.deadbolt_garagedoor
trigger: state
to: 'jammed'
conditions: []
actions:
- action: notify.charles
data:
title: "⚠️ Lock Jammed"
message: >-
{{ trigger.to_state.attributes.friendly_name }} reported jammed —
the bolt may not have engaged. Check the door.
data:
tag: "lock-jammed-{{ trigger.entity_id }}"
Pair that with the lock’s auto-relock feature and you get both a retry and a heads-up if the retry fails.
Other options: Keymaster-Basic, keymaster-lite, and Simple Lock
The two-horse race isn’t the whole field. A few lighter forks and alternatives come up in the “which Z-Wave lock add-in” forum threads:
- Keymaster-Basic — a stripped-down fork of Keymaster that drops the auto-generated Lovelace dashboards and some of the per-slot machinery in exchange for far fewer entities. It’s what people reach for when Keymaster’s entity sprawl is the main complaint but they don’t want to switch ecosystems.
- keymaster-lite — similar spirit: keep the core slot management, shed most of the heavy dashboard and helper generation.
- Simple Lock — a more minimal approach for people who just want to set and clear a PIN without time restrictions, usage tracking, or dashboards.
If your needs are basic — a couple of PINs, no calendars or usage limits — these avoid a lot of the clutter. If you want restrictions, notifications, and multi-lock sync, you’re back to Keymaster or Lock Code Manager.
Frequently asked questions
Can I run Keymaster and Lock Code Manager at the same time? On different locks, effectively yes — but never point both at the same lock, or they’ll fight over the same code slots. During a migration, cut one lock over at a time.
Does either one work without the internet? Yes. Both operate entirely locally through your Z-Wave/Zigbee radio, and the PINs live on the lock itself, so codes keep working even if Home Assistant or your internet is down.
If you want to skip the rest of this post: install Lock Code Manager, not Keymaster. Build slot-aware unlock notifications with a tag-based dedupe rather than a for: debounce. Cycle PINs.
Related guides: Three Schlage BE469ZP locks, years later · Layered home security in HA · Fixing an unreliable Zigbee mesh