Update for the New Smart Lock Code Management Experience and Beta Opportunity

New Smart Lock Code Management Experience

We are excited to share that we are working on a major upgrade around smart lock access codes in the SmartThings app.

We are transitioning away from the standalone Smart Lock Guest Access (SLGA) service on the Life tab and moving lock management directly into the lock’s device control card.

With this the SLGA service and lockCodes capability are being deprecated. Moving the functionality of SLGA to the individual lock device cards will provide easier use and enhanced functionality. This move will happen via a migration tool in the SmartThings app that will seamlessly migrate your existing lock codes.

As part of this transition we are also deprecating the lockCodes capability in favor of new and enhanced capabilities (lockUsers & lockCredentials). See below for the technical breakdown of how we are deprecating lockCodes capability.

If you are a developer who has built functionality using the lockCodes capability you will need to update your logic to use the new capabilities. If you have any questions please reach out to partners@smartthings.com or reply to this post.

Beta Tester Opportunity

We are looking for beta testers to test the SLGA migration process before it rolls out to everyone later this year. If you want to sign up to be a beta tester please fill out this form.

Eligibility & Requirements

To participate in this trial, you’ll need to meet the following criteria:

  • Device: A Hub-connected Smart Lock that supports programmable codes (Z-Wave, Zigbee, or supported Matter locks).

  • Active Codes: At least one access code programmed on your lock.

  • Current Setup: The Smart Lock Guest Access (SLGA) service installed on the Life tab of your SmartThings app.

  • App Version: Using the latest official production build from the Google Play Store or Apple App Store.

Please sign-up here. Once your account is enabled for early access, we will follow-up with specific instructions on how to test the migration and share your feedback. Thank you!

Door Lock lockCodes Capability Deprecation — Transition to lockUsers / lockCredentials

Audience: Developers and integrations that manage door-lock PIN codes through the SmartThings API (Devices API, Rules, subscriptions) using the lockCodes capability.

Summary: The lockCodes capability is being deprecated, and the services built on it will be shut down. The same functionality is provided by the lockUsers and lockCredentials capabilities. This model is already in use on Matter door locks and is now being extended to Zigbee / Z-Wave door locks. Please transition your integration following this guide.


1. Background

The legacy lockCodes capability bundled a user’s name and PIN under a single physical lock slot number (codeSlot). That structure could not express “one person with multiple credentials,” and command outcomes had to be parsed from string events (codeChanged).

To address this, SmartThings introduced a new model that separates the user (who) from the credential (what unlocks the door):

  • lockUsers — manages user identity (name, type)

  • lockCredentials — manages credentials (PINs, etc.)

This model is already in use on Matter door locks and now applies to Zigbee / Z-Wave door locks as well. From a developer’s point of view, you interact with door locks through one identical API, regardless of the underlying protocol.

Scope of this document: Since lockCodes was a PIN-code management feature, this transition guide is scoped to PIN (credentialType: "pin"). The new schema defines additional credential types, but actual device support must be checked via the supportedCredentials attribute.


2. What is changing

Item Before (lockCodes) After (lockUsers + lockCredentials)
Data model Name + PIN bound to one slot number (codeSlot) User (userIndex) separated from credential (credentialIndex)
Listing lockCodes attribute (JSON string map) users + credentials (structured arrays)
Command outcomes Parse codeChanged strings (e.g. “1 set”) Structured commandResult events with statusCode
Lock / unlock lock capability lock capability (unchanged)

Conceptual model

lockCodes (before) lockUsers + lockCredentials (after)
────────────────── ──────────────────────────────────
codeSlot ──► name + PIN userIndex ──► userName, userType

└── credentialIndex ──► credentialType("pin"),
credentialName
(PIN value travels only in command arguments)

  • A single user (userIndex) can own multiple credentials; each credential joins to its user via the userIndex field.

  • PIN handling: Values such as "1234" in this document are illustrative. PIN values are passed only as command arguments and — as before — are never included in attributes / device status (only metadata such as names and indexes is exposed).


3. New capability overview

3.1 lockUsers

Commands Arguments
addUser userName, userType
updateUser userIndex, userName, userType
deleteUser userIndex
deleteAllUsers
Attributes Description
users Array of registered users: [{ userIndex, userType, userName }]
totalUsersSupported Maximum number of users the device supports
commandResult Command outcome: { commandName, statusCode, userIndex? }
  • userType: adminMember | controlOnlyMember | guest — use guest for the typical named codes managed from an app/API.

  • Possible values of the commandResult fields:

    • commandName: addUser | updateUser | deleteUser | deleteAllUsers

    • statusCode: success | failure | occupied | invalidCommand | resourceExhausted | busy

    • userIndex: included with the target user index on success

// lockUsers.users example
[
{ "userIndex": 1, "userName": "Alice", "userType": "guest" },
{ "userIndex": 2, "userName": "Bob", "userType": "guest" }
]

3.2 lockCredentials

Commands Arguments
addCredential userIndex, userType, credentialType, credentialData, credentialName?
updateCredential userIndex, credentialIndex, credentialType, credentialData, credentialName?
deleteCredential credentialIndex, credentialType
deleteAllCredentials credentialType?
Attributes Description
credentials Array of registered credentials: [{ userIndex, credentialIndex, credentialType, credentialName }] — never contains PIN values
supportedCredentials Credential types the device supports (["pin"] for the devices in this transition)
pinUsersSupported Maximum number of PIN credentials
minPinCodeLen / maxPinCodeLen PIN length limits
commandResult Command outcome: { commandName, statusCode, userIndex?, credentialIndex? }

// lockCredentials.credentials example
[
{ "userIndex": 1, "credentialIndex": 1, "credentialType": "pin", "credentialName": "Alice" },
{ "userIndex": 2, "credentialIndex": 2, "credentialType": "pin", "credentialName": "Bob" }
]

  • Possible values of the commandResult fields:

    • commandName: addCredential | updateCredential | deleteCredential | deleteAllCredentials

    • statusCode: success | failure | occupied | duplicate | resourceExhausted | invalidCommand | busy

    • userIndex / credentialIndex: included with the target indexes on success

userIndex: 0 on addCredential means “create a new user and attach this PIN to it in one step.” Free indexes are assigned automatically, so clients no longer need to track or pick empty slot numbers — this replaces the old pattern of choosing a slot for setCode.

Important: When using addCredential, the userName is not automatically set. The SmartThings app only displays named users in the Manage Users screen. If a user remains unnamed they will be invisible in the app, which can make a successful registration appear to have failed and result in duplicate registration attempts. You should explicitly set the user’s name by calling the updateUser command immediately after registration.

3.3 Quick mapping table

Before (lockCodes) After
setCode (new PIN) lockCredentials.addCredential (typically userIndex: 0)
setCode (change PIN) lockCredentials.updateCredential
nameSlot (rename) lockUsers.updateUser
deleteCode lockUsers.deleteUser or lockCredentials.deleteCredential
updateCodes (bulk) Sequential individual commands
reloadAllCodes / requestCode Not needed — users / credentials attributes always hold current state
setCodeLength No equivalent command — read minPinCodeLen / maxPinCodeLen
lockCodes attribute users + credentials arrays
codeChanged / scanCodes commandResult + refreshed users / credentials
maxCodes totalUsersSupported and pinUsersSupported
minCodeLength / maxCodeLength / codeLength minPinCodeLen / maxPinCodeLen

4. Use-case transition guide

Send commands via the Devices API as before: POST /devices/{deviceId}/commands

UC-1. List registered codes

Before: parse the JSON string in the lockCodes attribute — { "1": "Alice", "5": "Bob" }

After: subscribe to the lockUsers.users and lockCredentials.credentials arrays and join them by userIndex for display. No query command (reloadAllCodes, etc.) is needed — the attributes always hold the complete, current list.

UC-2. Add a new PIN (the most common flow)

Before

{ "commands": [{ "component": "main", "capability": "lockCodes",
"command": "setCode", "arguments": [3, "1234", "Alice"] }] }

After — Note: The SmartThings app currently manages exactly one credential per user. To maintain compatibility and a consistent user experience with the app UI, you should create a new user for each new PIN.

The recommended flow for creating a new user and PIN is a streamlined process: create the credential with userIndex: 0 (which auto-assigns a free index), and then explicitly set the user’s name using that returned index.

Step 1: Create the credential (auto-assigns a new userIndex).

{ "commands": [{ "component": "main", "capability": "lockCredentials",
"command": "addCredential",
"arguments": [0, "guest", "pin", "1234", "Alice"] }] }

Step 2: Read the assigned userIndex from the commandResult.

{ "commandName": "addCredential", "statusCode": "success",
"userIndex": 3, "credentialIndex": 3 }

Step 3: Update the userName. This ensures the user is properly named and visible in the SmartThings app UI. Use the userIndex from Step 2:

{ "commands": [{ "component": "main", "capability": "lockUsers",
"command": "updateUser", "arguments": [3, "Alice", "guest"] }] }

Step 4 (Optional) Configure Access Schedule: If a door lock supports the lockSchedules capability, you can optionally restrict the user’s access to specific weekly recurring times or calendar date windows.

Before setting a schedule, determine which schedule types the device supports by inspecting the capability attributes:

  • weekDaySchedulesPerUser > 0: Weekly recurring schedules via setWeekDaySchedule are supported

  • yearDaySchedulesPerUser > 0: Date-range-based (calendar) schedules via setYearDaySchedule are supported.

  • Attribute is null or 0: The corresponding schedule type is unsupported and should be treated as unavailable in your client or UI logic.

  • Option A: Restrict access by weekly recurring windows (setWeekDaySchedule)
    Restricts the user’s entry to specific days of the week within a designated time window (e.g., allowing entry only on Mondays between 12:30 PM and 5:30 PM).

{ "commands": [{ "component": "main", "capability": "lockSchedules",
"command": "setWeekDaySchedule", "arguments": [ 1, 1, { "weekDays": ["Monday"], "startHour": 12, "startMinute": 30, "endHour": 17, "endMinute": 30} ] }] }

  • Option B: Restrict access by specific calendar dates (setYearDaySchedule)
    Restricts the user’s entry to a specific calendar date range (e.g., allowing entry starting from August 10, 2026, at 9:00 AM until August 15, 2026, at 6:00 PM).

{ "commands": [{"component": "main", "capability": "lockSchedules",
"command": "setYearDaySchedule", "arguments": [1, 1, { "localStartTime": "2026-08-10T09:00:00", "localEndTime": "2026-08-15T18:00:00"}] }] }

UC-3. Change an existing PIN

Before: setCode(3, "9999", "Alice") — resend to the same slot

After

{ "commands": [{ "component": "main", "capability": "lockCredentials",
"command": "updateCredential",
"arguments": [3, 3, "pin", "9999", "Alice"] }] }

Argument order: userIndex, credentialIndex, credentialType, credentialData, credentialName?. Take both indexes from the current credentials array.

UC-4. Rename only (no PIN change)

Before: nameSlot(3, "Bob") — and in some flows a rename required resending the PIN

After — modify user metadata without touching the PIN:

{ "commands": [{ "component": "main", "capability": "lockUsers",
"command": "updateUser", "arguments": [3, "Bob", "guest"] }] }

UC-5. Delete one code

Before: deleteCode(3)

After — choose by intent:

// (a) Delete the whole user — linked credentials are removed too
// (recommended for a "remove guest" UX)
{ "commands": [{ "component": "main", "capability": "lockUsers",
"command": "deleteUser", "arguments": [3] }] }
// (b) Delete only a specific PIN credential
{ "commands": [{ "component": "main", "capability": "lockCredentials",
"command": "deleteCredential", "arguments": [3, "pin"] }] }

Use deleteUser when your intent is “remove this person.” Because deleteUser also cleans up the linked credentials, commandResult events may be emitted on both capabilities.

UC-6. Delete everything

Before: repeated deleteCode, or updateCodes with empty values

After: lockUsers.deleteAllUsers() (clears users + credentials) or lockCredentials.deleteAllCredentials("pin") (clears PINs only)

UC-7. Bulk programming (replacing updateCodes)

There is no bulk command. Send addCredential / updateCredential / deleteCredential sequentially, waiting for each commandResult before sending the next. If you receive busy, retry after a short delay. (Door locks process one command at a time.)

UC-8. Capacity & PIN length limits

Before After
maxCodes totalUsersSupported, pinUsersSupported
minCodeLength / maxCodeLength / codeLength minPinCodeLen / maxPinCodeLen

We recommend validating PIN length against min/max on the client before calling addCredential.

UC-9. Observe “who unlocked”

Keep subscribing to the lock capability for lock/unlock events. The data payload changes as follows:

// Before
{ "value": "unlocked", "data": { "method": "keypad", "codeId": "3", "codeName": "Alice" } }
// After
{ "value": "unlocked", "data": { "method": "keypad", "userIndex": 3,
"userName": "Alice", "userType": "guest" } }

Before field After field
codeId (string) userIndex (number)
codeName userName
userType

UC-10. Remote lock / unlock

Unchanged. Continue using the lock capability’s lock / unlock commands.


5. Handling command results (commandResult) — the key behavioral change

  • An HTTP ACCEPTED response only means the hub received the command. Judge actual success from the commandResult event.

  • For commands that require communication with the physical lock (e.g. addCredential), the result is emitted asynchronously after the device responds.

statusCode Meaning Recommended handling
success Succeeded Finalize state from refreshed users / credentials
failure Generic failure Surface an error
duplicate The same PIN already exists Ask the user for a different PIN
occupied The index is already in use Use another index, or userIndex: 0 auto-allocation
resourceExhausted Maximum count exceeded Ask to delete existing entries first
invalidCommand Invalid command/arguments Check request parameters
busy Another command is in progress Retry after a short delay

duplicate is emitted only on lockCredentials.commandResult (it is not part of the lockUsers statusCode enum). See Section 3 for the full per-capability enumerations.


6. What happens to existing devices and data

For devices already using lockCodes, the platform automatically converts existing data to the new model. No data-migration work is required on your side.

  • Each registered code is converted into a user (userType: "guest") + PIN credential (credentialType: "pin") pair, and the code name is preserved as userName / credentialName.

  • Immediately after conversion, lockUsers.users / lockCredentials.credentials attribute events are published, so subscribed services receive the new lists right away.

  • :warning: Do not assume your previously tracked slot numbers match the new indexes. After the transition, always rebuild your mapping from the users / credentials attributes.

  • Codes registered directly on the lock’s keypad are also detected by the platform and reflected automatically as a Guest <index> user + credential. Local operations that bypass the app arrive through the same attribute events. (Zigbee / Z-Wave)

How to tell whether a device has transitioned — lockCodes.migrated

During the transition period, an already-transitioned device may still expose the lockCodes capability in its profile. In that case, check the lockCodes.migrated attribute (boolean) to decide which API to use:

Device state How to detect API to use
Transitioned, but profile still shows lockCodes lockCodes.migrated is true lockUsers / lockCredentials
Not yet transitioned lockCodes.migrated absent or not true lockCodes (as before)
New model only lockCodes not in the capability list lockUsers / lockCredentials

Important: If lockCodes.migrated is absent or not true, you must not send commands to the new capabilities (lockUsers, lockCredentials). Doing so can leave the device in an inconsistent state where the legacy lockCodes data and the new capability data diverge, potentially causing unexpected behavior for the end user.

Always check the migrated attribute before deciding which capability set to use.

  • Once migrated is true, lockCodes commands are no longer processed and the lockCodes attribute is no longer updated — read state only from the users / credentials attributes.

  • migrated is hidden in the SmartThings app UI, but it is a regular attribute — readable through the Devices API and subscriptions.

  • This is a transition-period signal: once lockCodes is fully removed from device profiles, the attribute disappears together with the capability, and detection reduces to checking for lockUsers / lockCredentials support.


7. Transition checklist

  1. Check for lockUsers + lockCredentials in the device’s capabilities (on devices that still show lockCodes, check lockCodes.migrated: true), and use only the new API on supporting devices

  2. Remove parsing of the lockCodes JSON string and codeChanged / scanCodes events

  3. Subscribe to lockUsers.users, lockCredentials.credentials, both commandResult attributes, and lock

  4. Replace setCode with addCredential (userIndex: 0 for new entries) / updateCredential

  5. Replace deleteCode with deleteUser (per person) or deleteCredential (per PIN)

  6. Replace nameSlot with updateUser

  7. Replace bulk processing (updateCodes) with sequential individual commands + busy retry

  8. Update unlock-history mapping from codeId / codeName to userIndex / userName

  9. Branch on commandResult.statusCode (duplicate, resourceExhausted, busy, …)

  10. Respect minPinCodeLen / maxPinCodeLen and pinUsersSupported / totalUsersSupported

  11. Remove hardcoded slot numbers — always look up indexes from the attributes


8. References

  • Official capability reference:

  • The concrete deprecation timeline and per-device rollout schedule will be shared in a follow-up announcement.

  • For questions, please use the comments on this announcement or the developer support channels.

Thank you,
– The Samsung SmartThings Team

How do we know when it’s ready to test in the hubs and devices ? Have signed up for the beta firmware.