Homey Apps SDK — Skill Guide
This skill helps you build apps for the Homey smart home platform. A Homey app is a Node.js bundle
that runs locally on Homey Pro (or in the cloud on Homey Cloud). Apps add Devices, Flow cards,
Widgets, and more to the Homey ecosystem.
Key Concepts
SDK version: Always use SDK v3 ("sdk": 3 in the manifest). SDK v3 is async/await everywhere.
Node.js: As of Homey v12.9.0, all platforms run Node.js 22.
Homey Compose: The build system that splits the monolithic app.json manifest into smaller
*.compose.json files. Never edit /app.json directly — edit the compose files instead.
Three core classes you extend in every app:
Homey.App — exported from /app.js, instantiated once on app start
Homey.Driver — exported from /drivers/<id>/driver.js, manages pairing and all device instances
Homey.Device — exported from /drivers/<id>/device.js, represents a single paired device
Homey CLI (npx homey) is the primary development tool:
homey app create — scaffold a new app
homey app run — run & debug live on a Homey (uninstalls on quit)
homey app install — install persistently for testing
homey app publish — publish to the Homey App Store
homey app driver create — interactively add a driver
homey app flow create — interactively add a Flow card
homey app validate — check manifest validity
Project Structure (Homey Compose)
com.example.myapp/
├─ .homeycompose/
│ ├─ app.json # Core manifest properties
│ ├─ capabilities/
│ │ └─ <custom_cap_id>.json # Custom capability definitions
│ ├─ flow/
│ │ ├─ triggers/<id>.json # App-level Flow trigger cards
│ │ ├─ conditions/<id>.json # App-level Flow condition cards
│ │ └─ actions/<id>.json # App-level Flow action cards
│ ├─ discovery/
│ │ └─ <id>.json # LAN discovery strategies (mDNS, SSDP, MAC)
│ └─ locales/
│ └─ en.json # App-level translations
├─ assets/
│ ├─ icon.svg # App icon (SVG)
│ └─ images/
│ ├─ small.png (250x175)
│ ├─ large.png (500x350)
│ └─ xlarge.png (1000x700)
├─ drivers/
│ └─ <driver_id>/
│ ├─ assets/
│ │ ├─ icon.svg # Driver icon
│ │ └─ images/ (small, large, xlarge)
│ ├─ device.js # Device class
│ ├─ driver.js # Driver class
│ ├─ driver.compose.json # Driver manifest
│ ├─ driver.flow.compose.json # Driver-specific Flow cards (optional)
│ └─ driver.settings.compose.json # Device settings (optional)
├─ widgets/
│ └─ <widget_id>/
│ ├─ public/
│ │ └─ index.html # Widget frontend
│ ├─ api.js # Widget API handlers
│ ├─ widget.compose.json # Widget definition
│ ├─ preview-dark.png # Widget preview (dark)
│ └─ preview-light.png # Widget preview (light)
├─ locales/
│ ├─ en.json
│ └─ nl.json
├─ settings/
│ └─ index.html # App settings page (optional)
├─ api.js # App Web API (optional, Pro only)
├─ app.js # App class
├─ env.json # Secret environment variables (gitignored!)
├─ README.txt # App Store long description (plain text, no markdown)
└─ .homeyignore # Files to exclude from publishing
How to Use This Skill
When creating a Homey app, follow this workflow:
- Determine the integration type — Is it a LAN device (Wi-Fi/mDNS/SSDP), cloud API (OAuth2/webhooks),
or wireless protocol (Z-Wave, Zigbee, 433MHz, BLE, IR, Matter)?
- Read the relevant reference file from
references/ for detailed patterns:
references/app-and-manifest.md — App class, manifest, settings, environment, i18n, permissions
references/drivers-and-devices.md — Driver/Device classes, pairing, capabilities, settings, discovery
references/flow-cards.md — Triggers, conditions, actions, arguments, tokens, device Flow cards
references/widgets.md — Dashboard widgets, widget settings, widget API
references/wireless-and-cloud.md — Wi-Fi/LAN discovery, OAuth2, webhooks, Z-Wave, Zigbee, BLE
references/publishing.md — App Store guidelines, icons, images, publishing flow
- Scaffold the project following the Homey Compose structure above
- Write the code using SDK v3 patterns (async/await,
this.homey.* managers)
- Validate with
homey app validate --level=publish
Scaffolding a New App
When the user asks to create a new Homey app, generate the full file set. At minimum you need:
/.homeycompose/app.json with id, version, compatibility, sdk, name, description, category, etc.
/app.js extending Homey.App
- At least one driver with
driver.js, device.js, and driver.compose.json
/locales/en.json for any translated strings
README.txt
The app ID must be in reverse domain notation (e.g., com.example.mydevice). Never use "homey" or
"athom" in the app ID.
Minimal app.js
'use strict';
const Homey = require('homey');
class MyApp extends Homey.App {
async onInit() {
this.log('MyApp has been initialized');
}
}
module.exports = MyApp;
Minimal driver.js
'use strict';
const Homey = require('homey');
class MyDriver extends Homey.Driver {
async onInit() {
this.log('MyDriver has been initialized');
}
async onPairListDevices() {
// Return an array of discovered devices
return [];
}
}
module.exports = MyDriver;
Minimal device.js
'use strict';
const Homey = require('homey');
class MyDevice extends Homey.Device {
async onInit() {
this.log('MyDevice has been initialized');
// Register capability listeners
this.registerCapabilityListener('onoff', async (value) => {
// Handle the on/off command
this.log('onoff changed to', value);
});
}
async onAdded() {
this.log('MyDevice has been added');
}
async onSettings({ oldSettings, newSettings, changedKeys }) {
this.log('MyDevice settings changed');
}
async onDeleted() {
this.log('MyDevice has been deleted');
}
}
module.exports = MyDevice;
Minimal driver.compose.json
{
"name": { "en": "My Device" },
"class": "socket",
"capabilities": ["onoff"],
"platforms": ["local", "cloud"],
"connectivity": ["cloud"],
"pair": [
{
"id": "list_devices",
"template": "list_devices",
"navigation": { "next": "add_devices" }
},
{
"id": "add_devices",
"template": "add_devices"
}
]
}
Critical Rules
- Never edit
/app.json directly — it is generated by Homey Compose from *.compose.json files.
- Never overwrite constructors on App, Driver, or Device — use
onInit() instead.
- Use
this.homey.* for timers — call this.homey.setInterval() / this.homey.setTimeout() instead
of the global versions, so timers are auto-cleared on app destroy (critical for Homey Cloud multi-tenancy).
- Device
data must be immutable and unique — use MAC addresses or serial numbers, never IP addresses.
Store changing properties in the device store or settings.
- Always handle promise rejections — unhandled rejections crash apps on Homey Cloud.
Use
.catch(this.error) for fire-and-forget promises.
- Access managers via
this.homey — e.g., this.homey.flow, this.homey.settings, this.homey.drivers.
- App instance from Device/Driver:
this.homey.app gives you the App instance.
- Environment variables: defined in
/env.json, accessed as Homey.env.VARIABLE_NAME (uppercase, string values only).
- ESM support: Homey supports ES modules. See the ESM guide if using
import/export syntax.
Homey Cloud Considerations
When targeting Homey Cloud ("platforms": ["local", "cloud"]):
- No local Wi-Fi (no mDNS/SSDP/MAC discovery)
- No App Web API (
api.js)
- No app-to-app communication
- No
homey:manager:api permission
- Must handle multi-tenancy: no global state, use
this.homey.* for timers
- Apps run in Docker;
homey app run requires Docker for cloud testing
- Unhandled promise rejections will crash the app
Quick Reference: Common Patterns
Polling a Cloud API
async onInit() {
this.pollInterval = this.homey.setInterval(() => {
this.pollDevice().catch(this.error);
}, 30000);
}
async onUninit() {
this.homey.clearInterval(this.pollInterval);
}
Setting Capability Values (Device → Homey)
await this.setCapabilityValue('measure_temperature', 22.5);
// For custom boolean capabilities, this auto-triggers Flow cards
Listening for Capability Changes (Homey → Device)
this.registerCapabilityListener('target_temperature', async (value, opts) => {
await this.api.setTemperature(value);
});
Firing a Flow Trigger
// In app.js onInit():
const myTrigger = this.homey.flow.getDeviceTriggerCard('my_event');
// Later, from a device:
await this.driver.myTrigger.trigger(this, { token_key: 'value' }, {});
Using Device Discovery (mDNS)
Read references/wireless-and-cloud.md for the full discovery pattern including
onDiscoveryResult, onDiscoveryAvailable, and onDiscoveryAddressChanged.
For detailed documentation on any topic, read the relevant reference file listed above.
The official documentation lives at https://apps.developer.homey.app/ and the SDK v3
API reference at https://apps-sdk-v3.developer.homey.app/.
1---2name: homey-app3description: Build, scaffold, debug, and publish apps for the Homey smart home platform using the Homey Apps SDK v3. Use this skill whenever the user mentions Homey, Homey Pro, Homey Cloud, Homey app development, Homey CLI, Homey Compose, Homey Flow cards, Homey device drivers, Homey capabilities, Homey widgets, Homey pairing, or any smart home app development targeting the Homey platform. Also trigger when the user wants to integrate a device brand or cloud service with Homey, create Flow automations, build dashboard widgets, or publish to the Homey App Store. Even if the user just says "I want to make a Homey app" or mentions a device they want to control with Homey, use this skill.4---56# Homey Apps SDK — Skill Guide78This skill helps you build apps for the Homey smart home platform. A Homey app is a Node.js bundle9that runs locally on Homey Pro (or in the cloud on Homey Cloud). Apps add **Devices**, **Flow cards**,10**Widgets**, and more to the Homey ecosystem.1112## Key Concepts1314**SDK version**: Always use SDK v3 (`"sdk": 3` in the manifest). SDK v3 is async/await everywhere.1516**Node.js**: As of Homey v12.9.0, all platforms run Node.js 22.1718**Homey Compose**: The build system that splits the monolithic `app.json` manifest into smaller19`*.compose.json` files. Never edit `/app.json` directly — edit the compose files instead.2021**Three core classes** you extend in every app:22- `Homey.App` — exported from `/app.js`, instantiated once on app start23- `Homey.Driver` — exported from `/drivers/<id>/driver.js`, manages pairing and all device instances24- `Homey.Device` — exported from `/drivers/<id>/device.js`, represents a single paired device2526**Homey CLI** (`npx homey`) is the primary development tool:27- `homey app create` — scaffold a new app28- `homey app run` — run & debug live on a Homey (uninstalls on quit)29- `homey app install` — install persistently for testing30- `homey app publish` — publish to the Homey App Store31- `homey app driver create` — interactively add a driver32- `homey app flow create` — interactively add a Flow card33- `homey app validate` — check manifest validity3435## Project Structure (Homey Compose)3637```38com.example.myapp/39├─ .homeycompose/40│ ├─ app.json # Core manifest properties41│ ├─ capabilities/42│ │ └─ <custom_cap_id>.json # Custom capability definitions43│ ├─ flow/44│ │ ├─ triggers/<id>.json # App-level Flow trigger cards45│ │ ├─ conditions/<id>.json # App-level Flow condition cards46│ │ └─ actions/<id>.json # App-level Flow action cards47│ ├─ discovery/48│ │ └─ <id>.json # LAN discovery strategies (mDNS, SSDP, MAC)49│ └─ locales/50│ └─ en.json # App-level translations51├─ assets/52│ ├─ icon.svg # App icon (SVG)53│ └─ images/54│ ├─ small.png (250x175)55│ ├─ large.png (500x350)56│ └─ xlarge.png (1000x700)57├─ drivers/58│ └─ <driver_id>/59│ ├─ assets/60│ │ ├─ icon.svg # Driver icon61│ │ └─ images/ (small, large, xlarge)62│ ├─ device.js # Device class63│ ├─ driver.js # Driver class64│ ├─ driver.compose.json # Driver manifest65│ ├─ driver.flow.compose.json # Driver-specific Flow cards (optional)66│ └─ driver.settings.compose.json # Device settings (optional)67├─ widgets/68│ └─ <widget_id>/69│ ├─ public/70│ │ └─ index.html # Widget frontend71│ ├─ api.js # Widget API handlers72│ ├─ widget.compose.json # Widget definition73│ ├─ preview-dark.png # Widget preview (dark)74│ └─ preview-light.png # Widget preview (light)75├─ locales/76│ ├─ en.json77│ └─ nl.json78├─ settings/79│ └─ index.html # App settings page (optional)80├─ api.js # App Web API (optional, Pro only)81├─ app.js # App class82├─ env.json # Secret environment variables (gitignored!)83├─ README.txt # App Store long description (plain text, no markdown)84└─ .homeyignore # Files to exclude from publishing85```8687## How to Use This Skill8889When creating a Homey app, follow this workflow:90911. **Determine the integration type** — Is it a LAN device (Wi-Fi/mDNS/SSDP), cloud API (OAuth2/webhooks),92 or wireless protocol (Z-Wave, Zigbee, 433MHz, BLE, IR, Matter)?932. **Read the relevant reference file** from `references/` for detailed patterns:94 - `references/app-and-manifest.md` — App class, manifest, settings, environment, i18n, permissions95 - `references/drivers-and-devices.md` — Driver/Device classes, pairing, capabilities, settings, discovery96 - `references/flow-cards.md` — Triggers, conditions, actions, arguments, tokens, device Flow cards97 - `references/widgets.md` — Dashboard widgets, widget settings, widget API98 - `references/wireless-and-cloud.md` — Wi-Fi/LAN discovery, OAuth2, webhooks, Z-Wave, Zigbee, BLE99 - `references/publishing.md` — App Store guidelines, icons, images, publishing flow1003. **Scaffold the project** following the Homey Compose structure above1014. **Write the code** using SDK v3 patterns (async/await, `this.homey.*` managers)1025. **Validate** with `homey app validate --level=publish`103104## Scaffolding a New App105106When the user asks to create a new Homey app, generate the full file set. At minimum you need:107108- `/.homeycompose/app.json` with id, version, compatibility, sdk, name, description, category, etc.109- `/app.js` extending `Homey.App`110- At least one driver with `driver.js`, `device.js`, and `driver.compose.json`111- `/locales/en.json` for any translated strings112- `README.txt`113114The app ID must be in reverse domain notation (e.g., `com.example.mydevice`). Never use "homey" or115"athom" in the app ID.116117### Minimal app.js118119```javascript120'use strict';121122const Homey = require('homey');123124class MyApp extends Homey.App {125 async onInit() {126 this.log('MyApp has been initialized');127 }128}129130module.exports = MyApp;131```132133### Minimal driver.js134135```javascript136'use strict';137138const Homey = require('homey');139140class MyDriver extends Homey.Driver {141 async onInit() {142 this.log('MyDriver has been initialized');143 }144145 async onPairListDevices() {146 // Return an array of discovered devices147 return [];148 }149}150151module.exports = MyDriver;152```153154### Minimal device.js155156```javascript157'use strict';158159const Homey = require('homey');160161class MyDevice extends Homey.Device {162 async onInit() {163 this.log('MyDevice has been initialized');164165 // Register capability listeners166 this.registerCapabilityListener('onoff', async (value) => {167 // Handle the on/off command168 this.log('onoff changed to', value);169 });170 }171172 async onAdded() {173 this.log('MyDevice has been added');174 }175176 async onSettings({ oldSettings, newSettings, changedKeys }) {177 this.log('MyDevice settings changed');178 }179180 async onDeleted() {181 this.log('MyDevice has been deleted');182 }183}184185module.exports = MyDevice;186```187188### Minimal driver.compose.json189190```json191{192 "name": { "en": "My Device" },193 "class": "socket",194 "capabilities": ["onoff"],195 "platforms": ["local", "cloud"],196 "connectivity": ["cloud"],197 "pair": [198 {199 "id": "list_devices",200 "template": "list_devices",201 "navigation": { "next": "add_devices" }202 },203 {204 "id": "add_devices",205 "template": "add_devices"206 }207 ]208}209```210211## Critical Rules2122131. **Never edit `/app.json` directly** — it is generated by Homey Compose from `*.compose.json` files.2142. **Never overwrite constructors** on App, Driver, or Device — use `onInit()` instead.2153. **Use `this.homey.*` for timers** — call `this.homey.setInterval()` / `this.homey.setTimeout()` instead216 of the global versions, so timers are auto-cleared on app destroy (critical for Homey Cloud multi-tenancy).2174. **Device `data` must be immutable and unique** — use MAC addresses or serial numbers, never IP addresses.218 Store changing properties in the device store or settings.2195. **Always handle promise rejections** — unhandled rejections crash apps on Homey Cloud.220 Use `.catch(this.error)` for fire-and-forget promises.2216. **Access managers via `this.homey`** — e.g., `this.homey.flow`, `this.homey.settings`, `this.homey.drivers`.2227. **App instance from Device/Driver**: `this.homey.app` gives you the App instance.2238. **Environment variables**: defined in `/env.json`, accessed as `Homey.env.VARIABLE_NAME` (uppercase, string values only).2249. **ESM support**: Homey supports ES modules. See the ESM guide if using `import`/`export` syntax.225226## Homey Cloud Considerations227228When targeting Homey Cloud (`"platforms": ["local", "cloud"]`):229- No local Wi-Fi (no mDNS/SSDP/MAC discovery)230- No App Web API (`api.js`)231- No app-to-app communication232- No `homey:manager:api` permission233- Must handle multi-tenancy: no global state, use `this.homey.*` for timers234- Apps run in Docker; `homey app run` requires Docker for cloud testing235- Unhandled promise rejections will crash the app236237## Quick Reference: Common Patterns238239### Polling a Cloud API240```javascript241async onInit() {242 this.pollInterval = this.homey.setInterval(() => {243 this.pollDevice().catch(this.error);244 }, 30000);245}246247async onUninit() {248 this.homey.clearInterval(this.pollInterval);249}250```251252### Setting Capability Values (Device → Homey)253```javascript254await this.setCapabilityValue('measure_temperature', 22.5);255// For custom boolean capabilities, this auto-triggers Flow cards256```257258### Listening for Capability Changes (Homey → Device)259```javascript260this.registerCapabilityListener('target_temperature', async (value, opts) => {261 await this.api.setTemperature(value);262});263```264265### Firing a Flow Trigger266```javascript267// In app.js onInit():268const myTrigger = this.homey.flow.getDeviceTriggerCard('my_event');269// Later, from a device:270await this.driver.myTrigger.trigger(this, { token_key: 'value' }, {});271```272273### Using Device Discovery (mDNS)274Read `references/wireless-and-cloud.md` for the full discovery pattern including275`onDiscoveryResult`, `onDiscoveryAvailable`, and `onDiscoveryAddressChanged`.276277---278279For detailed documentation on any topic, read the relevant reference file listed above.280The official documentation lives at https://apps.developer.homey.app/ and the SDK v3281API reference at https://apps-sdk-v3.developer.homey.app/.