# Widgets

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.

{% hint style="info" %}
Widgets do not work on Homey Cloud and require a [compatibility](https://apps.developer.homey.app/app/manifest#properties) of `>=12.3.0`.
{% endhint %}

## 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](https://apps.developer.homey.app/app/manifest#properties) of your app to `>=12.3.0` 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:

{% code title="/widgets/<widgetId>/widget.compose.json" %}

```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"
    }
  }
}
```

{% endcode %}

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](https://apps.developer.homey.app/the-basics/devices/settings). For a full overview see [Settings](https://apps.developer.homey.app/the-basics/widgets/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](https://apps.developer.homey.app/advanced/web-api) you have access to the `homey` instance of your app.

{% tabs %}
{% tab title="JavaScript" %}
{% code title="/widgets/<widgetId>/api.js" %}

```javascript
'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);
  },
};
```

{% endcode %}
{% endtab %}

{% tab title="TypeScript" %}
{% code title="/widgets/<widgetId>/api.mts" %}

```mts
import type App from "../../app.mjs";

type RequestWithBody = {
  homey: App["homey"];
  query: Record<string, string>;
  params: Record<string, string>;
  body: Record<string, unknown>;
};

type RequestWithoutBody = {
  homey: App["homey"];
  query: Record<string, string>;
  params: Record<string, string>;
  body: Record<never, never>; // Homey.API sends an empty body for GET and DELETE requests
};

export default {
  async getSomething({ homey, query }: RequestWithoutBody): Promise<any> {
    // 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 }: RequestWithBody): Promise<any> {
    // access the post body and perform some action on it.
    return (homey.app as App).addSomething(body);
  },

  async updateSomething({ homey, params, body }: RequestWithBody): Promise<any> {
    return (homey.app as App).updateSomething(body);
  },

  async deleteSomething({ homey, params }: RequestWithoutBody): Promise<any> {
    return (homey.app as App).deleteSomething(params.id);
  },
};

```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code title="/widgets/<widgetId>/api.py" %}

```python
from typing import Any, Never, cast

from homey.homey import Homey

from ...app import App


async def get_something(
    *,
    homey: Homey,
    query: dict[str, str],
    params: dict[str, str],
    body: dict[Never, Never],  # Homey.API sends an empty body for GET requests
) -> Any:
    return "Hello from App"


async def add_something(
    *, homey: Homey, query: dict[str, str], params: dict[str, str], body: dict[str, Any]
) -> Any:
    return cast(App, homey.app).add_something(body)


async def update_something(
    *, homey: Homey, query: dict[str, str], params: dict[str, str], body: dict[str, Any]
) -> Any:
    return cast(App, homey.app).update_something(body)


async def delete_something(
    *,
    homey: Homey,
    query: dict[str, str],
    params: dict[str, str],
    body: dict[Never, Never],  # Homey.API sends an empty body for DELETE requests
) -> Any:
    return cast(App, homey.app).delete_something(params["id"])


# Export all these methods as endpoints
__all__ = ["get_something", "add_something", "update_something", "delete_something"]

```

{% endcode %}
{% endtab %}
{% endtabs %}

### 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](https://www.figma.com/community/file/1392859749687789493/widget-previews-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`.

{% code title="index.html" %}

```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>
```

{% endcode %}

## Translations

Translations work the same as with custom pairing views or custom app settings views. See [Translating a string in Custom views](https://apps.developer.homey.app/app/internationalization#translating-a-string-in-custom-views).

## Styling

To ensure a unified and consistent look across your widgets, we have created [a CSS styling solution](https://apps.developer.homey.app/the-basics/widgets/styling).

## 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](https://apps.developer.homey.app/the-basics/widgets/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.

## Deprecating Widgets

Widgets can be deprecated by setting **`deprecated: true`** in their configuration. This prevents users from selecting the widget when adding new ones, though existing instances will remain functional.

## View API

### Homey.ready

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

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

### Homey.api

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

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

### Homey.on

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

Listen to events emitted by your app.

### Homey.\_\_

```javascript
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](https://apps.developer.homey.app/the-basics/app/internationalization).

### Homey.getWidgetInstanceId

```javascript
Homey.getWidgetInstanceId(): string
```

Get the unique id for the instance of the widget.

### Homey.getSettings

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

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

### Homey.setHeight

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

Change the widget height during runtime.

### Homey.popup

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

Open an in app browser view.

### Homey.hapticFeedback

```javascript
Homey.hapticFeedback(): void
```

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

### Homey.getDeviceIds

```javascript
Homey.getDeviceIds(): string[]
```

Retrieves the IDs of the devices selected by the user in the widget's **Devices** setting. See [#devices](https://apps.developer.homey.app/the-basics/settings#devices "mention")
