--- title: Widget Guide description: How to create a custom widget for Homepage. --- In this guide, we'll walk through the process of creating a custom widget for Homepage. We'll cover the basic structure of a widget, how to use translations, and how to fetch data from an API. By the end of this guide, you'll have a solid understanding of how to build your own custom widget. **Prerequisites:** - Basic knowledge of React and JavaScript - Familiarity with the Homepage platform - Understanding of JSON and API interactions Throughout this guide, we'll use `yourwidget` as a placeholder for the unique name of your custom widget. Replace `yourwidget` with the actual name of your widget. It should contain only lowercase letters and no spaces. This guide makes use of a fake API, which would return a JSON response as such, when called with the `v1/info` endpoint: ```json { "key1": 123, "key2": 456, "key3": 789 } ``` ## Set up the widget definition Create a new folder for your widget in the `src/widgets` directory. Name the folder `yourwidget`. Inside the `yourwidget` folder, create a new file named `widget.js`. This file will contain the metadata for your widget. Open the `widget.js` file and add the following code: ```js title="src/widgets/yourwidget/widget.js" import genericProxyHandler from "utils/proxy/handlers/generic"; // (1)! const widget = /* (2)! */ { api: "{url}/{endpoint}" /* (3)! */, proxyHandler: genericProxyHandler /* (1)! */, mappings: /* (4)! */ { info: /* (5)! */ { endpoint: "v1/info" /* (6)! */, }, }, }; export default widget; ``` 1. We import the `genericProxyHandler` from the `utils/proxy/handlers/generic` module. The `genericProxyHandler` is a generic handler that can be used to fetch data from an API. We then assign the `genericProxyHandler` to the `proxyHandler` property of the `widget` object. There are other handlers available that you can use depending on your requirements. You can also create custom handlers if needed. 2. We define a `widget` object that contains the metadata for the widget. 3. The API endpoint to fetch data from. You can use placeholders like `{url}` and `{endpoint}` to dynamically generate the API endpoint based on the widget configuration. 4. An object that contains mappings for different endpoints. Each mapping should have an `endpoint` property that specifies the endpoint to fetch data from. 5. A mapping named `info` that specifies the `v1/info` endpoint to fetch data from. This would be called from the component as such: `#!js useWidgetAPI(widget, "info");` 6. The `endpoint` property of the `info` mapping specifies the endpoint to fetch data from. There are other properties you can pass to the mapping, such as `method`, `headers`, and `body`. !!! warning "Important" All widgets that fetch data from dynamic endpoints should have either `mappings` or an `allowedEndpoints` property. ## Set up translation strings Homepage uses translated and localized strings for **all text and numerical content** in widgets. English is the default language, and other languages can be added via [Crowdin](https://crowdin.com/project/gethomepage). To add the English translations for your widget, follow these steps: Open the `public/locales/en/common.js` file. Add a new object for your widget to the bottom of the list, like this: ```json "yourwidget": { "key1": "Value 1", "key2": "Value 2", "key3": "Value 3" } ``` !!! note Even if you nativly speak another language, you should only add English translations. You can then add translations in your native language via [Crowdin](https://crowdin.com/project/gethomepage), once your widget is merged. ## Create the widget component Create a new file for your widgets component, named `component.jsx`, in the `src/widgets/yourwidget` directory. We'll build the contents of the `component.jsx` file step by step. First, we'll import the necessary dependencies: ```js title="src/widgets/yourwidget/component.jsx" linenums="1" import { useTranslation } from "next-i18next"; // (1)! import Container from "components/services/widget/container"; // (2)! import Block from "components/services/widget/block"; // (3)! import useWidgetAPI from "utils/proxy/use-widget-api"; // (4)! ``` 1. `#!js useTranslation()` is a hook provided by `next-i18next` that allows us to access the translation strings 2. `#!jsx ` and `#!jsx ` are custom components that we'll use to structure our widget. 3. `#!jsx ` and `#!jsx ` are custom components that we'll use to structure our widget. 4. `#!js useWidgetAPI(widget, endpoint)` is a custom hook that we'll use to fetch data from an API. --- Next, we'll define a functional component named `Component` that takes a `service` prop. ```js title="src/widgets/yourwidget/component.jsx" linenums="7" export default function Component({ service }) {} ``` --- We grab the helper functions from the `useTranslation` hook. ```js title="src/widgets/yourwidget/component.jsx" linenums="8" const { t } = useTranslation(); ``` --- We destructure the `widget` object from the `service` prop. The `widget` object contains the metadata for the widget, such as the API endpoint to fetch data from. ```js title="src/widgets/yourwidget/component.jsx" linenums="9" const { widget } = service; ``` --- Now, the fun part! We use the `useWidgetAPI` hook to fetch data from an API. The `useWidgetAPI` hook takes two arguments: the `widget` object and the API endpoint to fetch data from. The `useWidgetAPI` hook returns an object with `data` and `error` properties. ```js title="src/widgets/yourwidget/component.jsx" linenums="10" const { data, error } = useWidgetAPI(widget, "info"); ``` !!! tip "API Tips" You'll see here how part of the API url is built using the `url` and `endpoint` properties from the widget definition. In this case, we're fetching data from the `info` endpoint. The `info` endpoint is defined in the `mappings` object. So the full API endpoint will be `"{url}/v1/info"`. The mapping and endpoint are often the same, but must be defined regardless. --- Next, we check if there's an error or no data. If there's an error, we return a `Container` and pass it the `service` and `error` as props. The `Container` component will handle displaying the error message. ```js title="src/widgets/yourwidget/component.jsx" linenums="12" if (error) { return ; } ``` --- If there's no data, we return a `Container` component with three `Block` components, each with a `label`. ```js title="src/widgets/yourwidget/component.jsx" linenums="16" if (!data) { return ( ); } ``` This will render the widget with placeholders for the data, i.e., a skeleton view. !!! tip "Translation Tips" The `label` prop in the `Block` component corresponds to the translation key we defined earlier in the `common.js` file. All text and numerical content should be translated. --- If there is data, we return a `Container` component with three `Block` components, each with a `label` and a `value`. Here we use the `t` function from the `useTranslation` hook to translate the data values. The `t` function takes the translation key and an object with variables to interpolate into the translation string. We're using the `common.number` translation key to format the data values as numbers. This allows for easy localization of numbers, such as using commas or periods as decimal separators. There are a large number of `common` numerical translation keys available, which you can learn more about in the [Translation Guide](translations.md). ```js title="src/widgets/yourwidget/component.jsx" linenums="26" return ( ); ``` --- Here's the complete `component.jsx` file: ```js title="src/widgets/yourwidget/component.jsx" linenums="1" import { useTranslation } from "next-i18next"; import Container from "components/services/widget/container"; import Block from "components/services/widget/block"; import useWidgetAPI from "utils/proxy/use-widget-api"; export default function Component({ service }) { const { t } = useTranslation(); const { widget } = service; const { data, error } = useWidgetAPI(widget, "info"); if (error) { return ; } if (!data) { return ( ); } return ( ); } ``` ## Add the widget to the Homepage To add your widget to the Homepage, you need to register it in the `src/widgets/widgets.js` file. Open the `src/widgets/widgets.js` file and import the `Component` from your widget's `component.jsx` file. Please keep the alphabetical order. ```js // ... import yourwidget from "./yourwidget/widget"; // ... ``` Add `yourwidget` to the `widgets` object. Please keep the alphabetical order. ```js const widgets = { // ... yourwidget: yourwidget, // ... }; ``` You also need to add the widget to the `components` object in the `src/widgets/components.js` file. Open the `src/widgets/components.js` file and import the `Component` from your widget's `component.jsx` file. Please keep the alphabetical order. ```js const components = { // ... yourwidget: dynamic(() => import("./yourwidget/component")), // ... }; ``` ## Using the widget You can now use your custom widget in your Homepage. Open your `services.yaml` file and add a new service with the `yourwidget` widget. ```yaml - Services: - Your Widget: icon: yourwidget.svg href: https://example.com/ widget: type: yourwidget url: http://127.0.0.1:1337 ``` !!! tip "API Tips" You'll see here how part of the API url is built using the `url` and `endpoint` properties from the widget definition. We defined the api endpoint as `"{url}/{endpoint}"`. This is where the `url` is defined. So the full API endpoint will be `http://127.0.0.1:1337/{endpoint}`. --- That's it! You've successfully created a custom widget for Homepage. If you have any questions or need help, feel free to reach out to the Homepage community for assistance. Happy coding!