Getting Started - homebridge-plugins/homebridge-matter GitHub Wiki
Every Matter device registration follows this pattern:
import type { API } from 'homebridge'
export function registerMyDevice(api: API) {
const accessory = {
// Identity
uuid: api.matter.uuid.generate('unique-device-id'),
displayName: 'My Device',
deviceType: api.matter.deviceTypes.OnOffLight,
serialNumber: 'DEVICE-001',
manufacturer: 'My Company',
model: 'Model v1',
// Optional: Persistent context storage
context: {
deviceId: 'my-device-123',
},
// State: Initial values for all cluster attributes
// These values are only used when the accessory is first created
// After that, Homebridge automatically persists and restores state across restarts
clusters: {
onOff: {
onOff: false, // Initial state (only used on first creation)
},
},
// Handlers: Respond to commands from Home app
handlers: {
onOff: {
on: async () => {
// Control your physical device
},
off: async () => {
// Control your physical device
},
},
},
}
return [accessory]
}Every accessory needs a unique identification:
{
uuid: api.matter.uuid.generate('unique-id'), // Must be unique per device
displayName: 'Living Room Light', // Name shown in Home app
deviceType: api.matter.deviceTypes.OnOffLight, // Matter device type
serialNumber: 'LIGHT-001', // Unique serial number
manufacturer: 'My Company', // Manufacturer name
model: 'Smart Light v1', // Model identifier
}Access all Matter device types via api.matter.deviceTypes:
// Common examples
api.matter.deviceTypes.OnOffLight
api.matter.deviceTypes.DimmableLight
api.matter.deviceTypes.TemperatureSensor
api.matter.deviceTypes.Thermostat
api.matter.deviceTypes.DoorLock
// ... see full list belowClick to see all available device types
api.matter.deviceTypes.OnOffLight
api.matter.deviceTypes.DimmableLight
api.matter.deviceTypes.ColorTemperatureLight
api.matter.deviceTypes.ExtendedColorLightapi.matter.deviceTypes.OnOffSwitch
api.matter.deviceTypes.OnOffOutletapi.matter.deviceTypes.AirQualitySensor
api.matter.deviceTypes.TemperatureSensor
api.matter.deviceTypes.HumiditySensor
api.matter.deviceTypes.LightSensor
api.matter.deviceTypes.MotionSensor
api.matter.deviceTypes.ContactSensor
api.matter.deviceTypes.LeakSensor
api.matter.deviceTypes.SmokeSensorapi.matter.deviceTypes.Thermostat
api.matter.deviceTypes.Fan
api.matter.deviceTypes.RoomAirConditionerapi.matter.deviceTypes.DoorLockapi.matter.deviceTypes.WindowCoveringapi.matter.deviceTypes.RoboticVacuumCleanerapi.matter.deviceTypes.GenericSwitch
api.matter.deviceTypes.PumpStore custom data that persists across Homebridge restarts:
{
context: {
deviceId: 'my-light-123',
lastKnownState: true,
customData: { /* anything */ }
},
}
// Access later:
const deviceId = accessory.context.deviceIdDefine initial state for all clusters your device supports:
clusters: {
onOff: {
onOff: false, // Boolean: true = on, false = off
},
levelControl: {
currentLevel: 127, // Number: 1-254 (127 = 50%)
minLevel: 1, // Minimum brightness
maxLevel: 254, // Maximum brightness
},
}- These values are initial/default values only - used when the accessory is first created
- Once created, Homebridge automatically persists all state changes
- On restart, Homebridge restores the last known state, not these initial values
- State persists across Homebridge restarts, updates, and system reboots
- This works the same way as HAP - you don't need to manually save/restore state
Example lifecycle:
- First run: Accessory created with
onOff: false(your initial value) - User turns light on in Home app → state becomes
onOff: true - Homebridge restarts → state is still
onOff: true(persisted) - Not reset to
onOff: false(initial values are ignored after first creation)
Here's a complete working example that connects to a hypothetical HTTP-based smart light:
import type { API } from 'homebridge'
import axios from 'axios'
export function registerLight(api: API, deviceId: string, ipAddress: string) {
const accessory = {
// Identity
uuid: api.matter.uuid.generate(`http-light-${deviceId}`),
displayName: `Light ${deviceId}`,
deviceType: api.matter.deviceTypes.OnOffLight,
serialNumber: `HTTP-${deviceId}`,
manufacturer: 'Generic HTTP',
model: 'v1.0',
// Context: Store device information
context: {
deviceId,
ipAddress,
},
// Initial state (only used on first creation)
clusters: {
onOff: {
onOff: false,
},
},
// Handlers: Control the physical device
handlers: {
onOff: {
on: async () => {
// Turn on the physical light
await axios.post(`http://${ipAddress}/api/light`, {
state: 'on',
})
// Homebridge automatically updates Matter state to onOff: true
},
off: async () => {
// Turn off the physical light
await axios.post(`http://${ipAddress}/api/light`, {
state: 'off',
})
// Homebridge automatically updates Matter state to onOff: false
},
},
},
}
return [accessory]
}In your platform plugin, you must register ALL accessories (including cached ones) on every startup:
import type { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig } from 'homebridge'
export class MyPlatform implements DynamicPlatformPlugin {
private accessories: MatterAccessory[] = []
constructor(
public readonly log: Logger,
public readonly config: PlatformConfig,
public readonly api: API,
) {
this.api.on('didFinishLaunching', async () => {
// Discover/create your Matter accessories
const accessories = this.discoverAccessories()
// IMPORTANT: Register ALL accessories on every startup
// Unlike HAP, cached accessories must be re-registered
await this.api.matter.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories)
})
}
configureAccessory(accessory: PlatformAccessory) {
// Required by DynamicPlatformPlugin interface
// For Matter: Store reference to rebuild and re-register in didFinishLaunching
this.accessories.push(accessory)
}
private discoverAccessories(): MatterAccessory[] {
// Discover devices from your API/network
// Rebuild Matter accessories from cached accessories
// Return ALL accessories (new + cached)
return []
}
}Important Difference from HAP:
-
HAP: Cached accessories are automatically restored via
configureAccessory()callback -
Matter: You must call
registerPlatformAccessories()for all accessories (cached + new) on every plugin startup