Skip to main content

💻 Developer Guides


Hold on! This is a technical documentation that explains the logic and structure of Homarr. A certain degree of technical knowledge about programming is required to interpret these informations.

If you're looking for the installation guide, please use our detailed documentation.

Architecture overview

The following UML package diagram shows the relations and dependencies, that Homarr has. Each arrow indicates a relation between two components. The arrow itself shows the direction of communication.

Technology stack

Is is a coarse list of the technologies, that are in use and how we use them.

  • Next.js: React framework that enables us to render on the server, build APIs and authenticate.
  • Mantine: Complex UI library, that provides useful components, easy inline styling, custom forms and more. Can easily be extended and modified for Homarr's design.
  • Tablericons: Vast collection of minimal and modern icons
  • Axios: Promise based HTTP client that is browser and node.js compatible.
  • I18N: Internationalization framework that provides globalisation and localization
  • Zustand: Client-side state management for React
  • Zod: Strict Typescript schema validation for the browser and node.js
  • Tanstack: Several packages for asynchronous storages and queries
  • Emotion: Inline CSS

There are other packages as well, but those are not of significant importance to the architecture. If you want to know those technologies are used for production builds & deplyoments, check out the deployments section.

Online documentation

The above only covers the application itself. For the online documentation, we are using different technologies to remove as much overhead as possible.

As the base, we use Docusaurus to write documentation fast and efficently. The documentation is open-source and can be edited by anyone:

Since many searching solutions require licensing or expensive cloud services, we use the docusaurus-search-local plugin that builds the nessesary data at build.

Graphs can be written using mermaid.js, which were in fact used for this page.

Principles and conventions

During development and production builds, we are enforcing our ESLint rules. Any errors will be marked automatically, if you have the correct extensions installed.

Development environments & extensions

All maintainers are using VisualStudio Code. Although it is possible to use other IDEs (like Jetbrain's), we advise against using them, as some extensions may not be available in other IDEs.

The minimal setup includes the following extensions:

Additional extensions may include:


All of our repositories contain the required files to tell extensions, what rules should be applied. You do not need to make additional configurations. Please do not commit any files in the .vscode directory or other of your personal extensions.

Contribution workflow


Although this is not documented strictly in our workflow, it is recommended that you make frequent commits using git.


The English translation preset will be translated automatically in other languages using our community-driven translation program. For this reason, we will not accept any pull request with modifications to translation files other than additions or removals in the English template.


Deployments are done using GitHub's action system. The build is created on Ubuntu (latest), which builds the project and pushes the finalized docker image to GHCR.

As a developer, you do not need to know anything about what each of these steps specifically does. However, it is recommended that you know the differences between development and production builds.

Creating a new widget

Creating your own widget may look overwheling at first, but using this guide, it will be very easy.


Create the widget

  • Widgets are located in the directory /src/widgets. Choose a name for your widget and create a directory with this name
  • Inside the directory, create a new .tsx file, that has the name of the widget, suffixed by WidgetTile.tsx (example: VideoStreamWidgetTile.tsx)
  • Copy and paste the following code in your new file:
    const definition = defineWidget({
    id: 'your-widget-name',
    icon: IconClock,
    options: {},
    gridstack: {
    minWidth: 1,
    minHeight: 1,
    maxWidth: 12,
    maxHeight: 12,
    component: YourWidgetTile,

    export type IYourWidget = IWidget<(typeof definition)['id'], typeof definition>;

    interface YourWidgetTileProps {
    widget: IYourWidget;

    function YourWidgetTile({ widget }: YourWidgetTileProps) {
    /* hooks go here */

    /* return JSX */
    return (
    <span>Hello World</span>

    export default definition;
  • Replace all YourWidget in the above example with your widget name. Now you can start writing the UI of your widget.
  • If your widget makes requests over the network, it is recommended that requests are sent from Homarr instead of the client. This ensures that credentials will stay on the server. You can write new APIs for the widgets in the /src/pages/api/modules/ directory. You can look at the other routes for good examples.


Homarr has a built-in secrets management system. They are stored in the same JSON configuration, where your dashboard is configured & saved. For this reason, you should never share your config directly or commit it via git.

When the config is being sent to the client, the credentials are removed by the following code:


Public properties in the configuration will not be removed from the response. All passwords, usernames and tokens should be marked as private, so they get removed. Public properties can be used for public encryption keys or other values, that do not expose any important information.


Although this is already a big improvements to previous systems, it is still not considered 100% safe. In future versions of Homarr, the JSON configuration will be replaced with a relational database, that will enable us to never read out credentials, when sending data to the client.

Helper components


When making something in homarr, if you wish to add some information for the user, you may do so using the InfoCard. An element preprogrammed and easy to implement.

The element includes the info Icon and the hover card. Simply enter your message and the link if there is one.

Simply insert it as any other element:

import { InfoCard } from '~/components/InfoCard/InfoCard'

<InfoCard message="Leave empty to keep the title hidden." link=""/>
Component Prop
Component PropDescriptionInput valueDefault state
messageMessage in plain text to be shown to the user upon hover.TextObligatory value upon using the element
linkLink shown at the end of the text under the translated text "See More...", if left empty the "See More..." will not show.URLOptionnal value
bgThe background of the HoverCard is by default set to follow the Dark/Light modes, but can be changed using this prop.ColorOptionnal value
PositionPosition from where the HoverCard will open.
  • bottom
  • bottom-end
  • bottom-start
  • left
  • left-end
  • left-start
  • right
  • right-end
  • right-start
  • top
  • top-end
  • top-start
Optionnal value, default at Top
hoverPropProp directly linked to the HoverCard element in the InfoCard.Partial<HoverCardProp>-
cardPropProp directly linked to the RichTextEditor element in the InfoCardPartial<RichTextEditorProps>-

If you wish for more control over the hovercard, go check it out directly in src/components/InfoCard/InfoCard.tsx.

Widget options

The InfoCard is already implemented onto the Widget's Options. If your widget option is a bit ambiguous, you can give more information by activating it's infocard automatically like in the following exemple that will directly use the translation point, using info: true.

This exemple is the one that leads to the first screenshot. It shows it being implemented onto the text widget option but is in fact available on ALL widget options.

As stated before, it supports links and here you just need to add it onto the infoLink component to which we recommend linking to the Homarr docs.

const definition = defineWidget({
id: '',
icon: Icon,
options: {
name: {
type: 'text',
defaultValue: '',
info: true,
infoLink: '',