Widgets

Widgets enable the creation of custom webviews for user dashboards.

Homey Apps can include custom widgets, which are standard web pages (HTML/CSS/JS) displayed to your app's users. These pages have access to a global Homey API, allowing them to communicate with Homey and your app. For instance, you could create a garbage collection calendar widget to show users the next pickup date.

Widgets do not work on Homey Cloud and require a compatibility of >=12.1.2.

Creating a Widget

To create a widget, run the homey app widget create command in the Homey CLI. This command will prompt you for your widget's name and ID after which it will create a widget under /widgets/<widgetId>/. Increase the compatibility of your app to >=12.1.2 since widget support was added in that release. The widget will contain the following files:

  • widget.compose.json

  • public/index.html

  • api.js

  • preview-dark.png

  • preview-light.png

widget.compose.json

The widget.compose.json file contains the definition of your widget. Here, you can define the name, settings, height, transparent and API properties of the widget:

/widgets/<widgetId>/widget.compose.json
{
  "name": {
    "en": "My Widget"
  },
  "settings": [
    {
      "id": "my-id",
      "type": "text",
      "title": {
        "en": "My Title"
      }
    }
  ],
  "height": 100,
  "api": {
    "getSomething": {
      "method": "GET",
      "path": "/"
    },
    "addSomething": {
      "method": "POST",
      "path": "/"
    },
    "updateSomething": {
      "method": "PUT",
      "path": "/:id"
    },
    "deleteSomething": {
      "method": "DELETE",
      "path": "/:id"
    }
  }
}

Widgets can have settings that users can change while selecting or editing a widget. This allows you to add some initial variables to your widgets. These work almost the same as device settings. For a full overview see Settings.

A height can be provided to set the initial height of the widget on load. It's also possible to provide the height during runtime. It's not advised to use both as that would cause shifts in the height during load. After first load the height will be cached so next loads will have no layout shifts. If the height is a number it is treated as an absolute value. If the height is in percentage it's used as an aspect ratio. So a height of 100% means a square widget.

In addition to setting the widget's height, you can also configure the widget's background transparency by setting the transparent property. By default, widgets have an opaque background, but setting transparent: true allows you to make the widget background fully transparent.

The api field contains the specification of your widget's API. This can be used to communicate with your app. These endpoints are scoped to the widget and not global.

index.html

The index.html file will be the entry point to your widget. This file will be loaded as soon as a user's dashboard requests your widget. Anything under the public folder will be hosted on the user's Homey so place assets that are referenced from your index.html there.

api.js

The api.js file contains the implementation of your API as defined in the widget.compose.json file. For each endpoint, you can add an implementation that performs any desired actions. As with the default app api you have access to the homey instance of your app.

/widgets/<widgetId>/api.js
'use strict';

module.exports = {
  async getSomething({ homey, query }) {
    // you can access query parameters like "/?foo=bar" through `query.foo`

    // you can access the App instance through homey.app
    // const result = await homey.app.getSomething();
    // return result;

    // perform other logic like mapping result data

    return 'Hello from App';
  },

  async addSomething({ homey, body }) {
    // access the post body and perform some action on it.
    return homey.app.addSomething(body);
  },

  async updateSomething({ homey, params, body }) {
    return homey.app.setSomething(body);
  },

  async deleteSomething({ homey, params }) {
    return homey.app.deleteSomething(params.id);
  },
};

preview-<mode>.png

The images preview-dark.png and preview-light.png are used for previewing your widget in dark and light modes, respectively. Ensure they accurately represent the appearance of your widget. You can use the following Figma template as a starter template.

Widget Instance

Each time a user adds a widget to a dashboard a unique id is generated, this id can be accessed via the Homey.getWidgetInstanceId() method. This can for example be used to save some data on the app side. On initial load of your widget a loading state will be displayed. When you call Homey.ready() this state will be removed so you can use this to do some initialization for your widget. This method can optionally be called with an object that specifies the height of the widget (Homey.ready({ height: 200 })). This height will override the height defined in the widget.compose.json.

index.html
<html>

<head>
...
</head>

<body>
  <div id="message"></div>
  <button id="my-button">Button</button>

  <script type="text/javascript">
    function onHomeyReady(Homey) {
      Homey.ready({ height: 200 });

      console.log('instanceId: ', Homey.getWidgetInstanceId());
      console.log('settings', Homey.getSettings());

      document.getElementById('my-button').addEventListener('click', () => {
        Homey.api('GET', '/', {})
          .then((result) => {
            document.getElementById('message').innerText = String(result);
          })
          .catch(console.error);
      });
    }
  </script>
</body>

</html>

Translations

Translations work the same as with custom pairing views or custom app settings views. See Translating a string in Custom views.

Styling

To ensure a unified and consistent look across your widgets, we have created a CSS styling solution.

Debugging

To enhance the developer experience, you can attach a webview debugger during widget development, which allows for inspecting and debugging your widget through tools like Chrome DevTools. For more details, see Debugging.

When running homey app run, a refresh button will appear, allowing you to reload the index.html file without restarting the entire app.

  • Docker Requirement: Docker is required to support this functionality, and homey app run will automatically enforce Docker usage.

  • Compatibility:

    • Supported Models: This feature works only on Homey 2023 and later models.

    • Unsupported Scenarios:

      • It will not function on Homey models earlier than 2023.

      • It is not available when running the app remotely using homey app run --remote.

This applies only to files in the public folder, streamlining the testing process by enabling quick reloads and reducing full app restarts during development. For other file changes, a full restart is required.

View API

Homey.ready

Homey.ready(args?: { height: number | string }): void

Call this method when your widget is ready to be shown.

Homey.api

Homey.api(method: string, path: string, body?: object): Promise<unknown>

Access your api as defined under widget.compose.json -> api.

Homey.on

Homey.on(event: string, callback: (...args[]: any) => void): void

Listen to events emitted by your app.

Homey.__

Homey.__(input: string, tokens?: object): string

Translate a string programmatically. The first argument input is the name in your /locales/__language__.json. Use dots to get a sub-property, e.g. settings.title. The optional second argument tokens is an object with replacers. Read more about translations in the internationalization guide.

Homey.getWidgetInstanceId

Homey.getWidgetInstanceId(): string

Get the unique id for the instance of the widget.

Homey.getSettings

Homey.getSettings(): { [key: string]: unknown }

Get the settings for your widget as filled in by the user.

Homey.setHeight

Homey.setHeight(height: number | string | null): Promise<void>

Change the widget height during runtime.

Homey.popup

Homey.popup(url: string): Promise<void>

Open an in app browser view.

Homey.hapticFeedback

Homey.hapticFeedback(): void

Provide a haptic feedback on presses. This function can only be called in a short window after a touch event.

Last updated