Skip to content

User Interface Mixin

User Interface Mixin

The UserInterfaceMixin class provides a set of methods to implement custom functionality for the InvenTree web interface.

Enable User Interface Mixin

To enable user interface plugins, the global setting ENABLE_PLUGINS_INTERFACE must be enabled, in the plugin settings.

Custom UI Features

The InvenTree user interface functionality can be extended in various ways using plugins. Multiple types of user interface features can be added to the InvenTree user interface.

The entrypoint for user interface plugins is the UserInterfaceMixin class, which provides a number of methods which can be overridden to provide custom functionality. The get_ui_features method is used to extract available user interface features from the plugin:

Return a list of custom features to be injected into the UI.

Parameters:

Name Type Description Default
feature_type FeatureType

The type of feature being requested

required
context dict

Additional context data provided by the UI (query parameters)

required
request Request

HTTPRequest object (including user information)

required

Returns:

Name Type Description
list list[UIFeature]

A list of custom UIFeature dicts to be injected into the UI

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def get_ui_features(
    self, feature_type: FeatureType, context: dict, request: Request, **kwargs
) -> list[UIFeature]:
    """Return a list of custom features to be injected into the UI.

    Arguments:
        feature_type: The type of feature being requested
        context: Additional context data provided by the UI (query parameters)
        request: HTTPRequest object (including user information)

    Returns:
        list: A list of custom UIFeature dicts to be injected into the UI

    """
    feature_map = {
        'dashboard': self.get_ui_dashboard_items,
        'panel': self.get_ui_panels,
        'template_editor': self.get_ui_template_editors,
        'template_preview': self.get_ui_template_previews,
    }

    if feature_type in feature_map:
        return feature_map[feature_type](request, context, **kwargs)
    else:
        logger.warning(f'Invalid feature type: {feature_type}')
        return []

Note here that the get_ui_features calls other methods to extract the available features from the plugin, based on the requested feature type. These methods can be overridden to provide custom functionality.

Implementation

Your custom plugin does not need to override the get_ui_features method. Instead, override one of the other methods to provide custom functionality.

UIFeature Return Type

The get_ui_features method should return a list of UIFeature objects, which define the available user interface features for the plugin. The UIFeature class is defined as follows:

Base type definition for a ui feature.

Attributes:

Name Type Description
key str

The key of the feature (required, must be a unique identifier)

title str

The title of the feature (required, human readable)

description str

The long-form description of the feature (optional, human readable)

icon str

The icon of the feature (optional, must be a valid icon identifier)

feature_type FeatureType

The feature type (required, see documentation for all available types)

options dict

Feature options (required, see documentation for all available options for each type)

context dict

Additional context data to be passed to the rendering function (optional, dict)

source str

The source of the feature (required, path to a JavaScript file, with optional function name).

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class UIFeature(TypedDict):
    """Base type definition for a ui feature.

    Attributes:
        key: The key of the feature (required, must be a unique identifier)
        title: The title of the feature (required, human readable)
        description: The long-form description of the feature (optional, human readable)
        icon: The icon of the feature (optional, must be a valid icon identifier)
        feature_type: The feature type (required, see documentation for all available types)
        options: Feature options (required, see documentation for all available options for each type)
        context: Additional context data to be passed to the rendering function (optional, dict)
        source: The source of the feature (required, path to a JavaScript file, with optional function name).
    """

    key: str
    title: str
    description: str
    icon: str
    feature_type: FeatureType
    options: dict
    context: dict
    source: str

Note that the options field contains fields which may be specific to a particular feature type - read the documentation below on each feature type for more information.

Dynamic Feature Loading

Each of the provided feature types can be loaded dynamically by the plugin, based on the information provided in the API request. For example, the plugin can choose to show or hide a particular feature based on the user permissions, or the current state of the system.

For examples of this dynamic feature loading, refer to the sample plugin implementation which demonstrates how to dynamically load custom panels based on the provided context.

Javascript Source Files

The rendering function for the custom user interface features expect that the plugin provides a Javascript source file which contains the necessary code to render the custom content. The path to this file should be provided in the source field of the UIFeature object.

Note that the source field can include the name of the function to be called (if this differs from the expected default function name).

For example:

"source": "/static/plugins/my_plugin/my_plugin.js:my_custom_function"

Available UI Feature Types

The following user interface feature types are available:

Dashboard Items

The InvenTree dashboard is a collection of "items" which are displayed on the main dashboard page. Custom dashboard items can be added to the dashboard by implementing the get_ui_dashboard_items method:

Return a list of custom dashboard items to be injected into the UI.

Parameters:

Name Type Description Default
request Request

HTTPRequest object (including user information)

required

Returns:

Name Type Description
list list[UIFeature]

A list of custom dashboard items to be injected into the UI

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
125
126
127
128
129
130
131
132
133
134
135
136
137
def get_ui_dashboard_items(
    self, request: Request, context: dict, **kwargs
) -> list[UIFeature]:
    """Return a list of custom dashboard items to be injected into the UI.

    Args:
        request: HTTPRequest object (including user information)

    Returns:
        list: A list of custom dashboard items to be injected into the UI
    """
    # Default implementation returns an empty list
    return []

Dashboard Item Options

The options field in the returned UIFeature object can contain the following properties:

Options type definition for a custom dashboard item.

Attributes:

Name Type Description
width int

The minimum width of the dashboard item (integer, defaults to 2)

height int

The minimum height of the dashboard item (integer, defaults to 2)

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
55
56
57
58
59
60
61
62
63
64
class CustomDashboardItemOptions(TypedDict):
    """Options type definition for a custom dashboard item.

    Attributes:
        width: The minimum width of the dashboard item (integer, defaults to 2)
        height: The minimum height of the dashboard item (integer, defaults to 2)
    """

    width: int
    height: int

Source Function

The frontend code expects a path to a javascript file containing a function named renderDashboardItem which will be called to render the custom dashboard item. Note that this function name can be overridden by appending the function name in the source field of the UIFeature object.

Example

Refer to the sample plugin for an example of how to implement server side rendering for custom panels.

Panels

Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the get_ui_panels method:

Return a list of custom panels to be injected into the UI.

Parameters:

Name Type Description Default
request Request

HTTPRequest object (including user information)

required

Returns:

Name Type Description
list list[UIFeature]

A list of custom panels to be injected into the UI

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
111
112
113
114
115
116
117
118
119
120
121
122
123
def get_ui_panels(
    self, request: Request, context: dict, **kwargs
) -> list[UIFeature]:
    """Return a list of custom panels to be injected into the UI.

    Args:
        request: HTTPRequest object (including user information)

    Returns:
        list: A list of custom panels to be injected into the UI
    """
    # Default implementation returns an empty list
    return []

Panel Options

The options field in the returned UIFeature object can contain the following properties:

Options type definition for a custom panel.

Attributes:

Name Type Description
icon

The icon of the panel (optional, must be a valid icon identifier).

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
47
48
49
50
51
52
class CustomPanelOptions(TypedDict):
    """Options type definition for a custom panel.

    Attributes:
        icon: The icon of the panel (optional, must be a valid icon identifier).
    """

Source Function

The frontend code expects a path to a javascript file containing a function named renderPanel which will be called to render the custom panel. Note that this function name can be overridden by appending the function name in the source field of the UIFeature object.

Example

Refer to the sample plugin for an example of how to implement server side rendering for custom panels.

Template Editors

The get_ui_template_editors feature type can be used to provide custom template editors.

Return a list of custom template editors to be injected into the UI.

Parameters:

Name Type Description Default
request Request

HTTPRequest object (including user information)

required

Returns:

Name Type Description
list list[UIFeature]

A list of custom template editors to be injected into the UI

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
139
140
141
142
143
144
145
146
147
148
149
150
151
def get_ui_template_editors(
    self, request: Request, context: dict, **kwargs
) -> list[UIFeature]:
    """Return a list of custom template editors to be injected into the UI.

    Args:
        request: HTTPRequest object (including user information)

    Returns:
        list: A list of custom template editors to be injected into the UI
    """
    # Default implementation returns an empty list
    return []

Template previews

The get_ui_template_previews feature type can be used to provide custom template previews:

Return a list of custom template previews to be injected into the UI.

Parameters:

Name Type Description Default
request Request

HTTPRequest object (including user information)

required

Returns:

Name Type Description
list list[UIFeature]

A list of custom template previews to be injected into the UI

Source code in src/backend/InvenTree/plugin/base/ui/mixins.py
153
154
155
156
157
158
159
160
161
162
163
164
165
def get_ui_template_previews(
    self, request: Request, context: dict, **kwargs
) -> list[UIFeature]:
    """Return a list of custom template previews to be injected into the UI.

    Args:
        request: HTTPRequest object (including user information)

    Returns:
        list: A list of custom template previews to be injected into the UI
    """
    # Default implementation returns an empty list
    return []

Plugin Context

When rendering certain content in the user interface, the rendering functions are passed a context object which contains information about the current page being rendered. The type of the context object is defined in the PluginContext file:

Plugin Context
import {
  type MantineColorScheme,
  type MantineTheme,
  useMantineColorScheme,
  useMantineTheme
} from '@mantine/core';
import type { AxiosInstance } from 'axios';
import { useMemo } from 'react';
import { type NavigateFunction, useNavigate } from 'react-router-dom';

import type { QueryClient } from '@tanstack/react-query';
import { api, queryClient } from '../../App';
import { useLocalState } from '../../states/LocalState';
import {
  type SettingsStateProps,
  useGlobalSettingsState,
  useUserSettingsState
} from '../../states/SettingsState';
import { type UserStateProps, useUserState } from '../../states/UserState';

/**
 * A set of properties which are passed to a plugin,
 * for rendering an element in the user interface.
 *
 * @param api - The Axios API instance (see ../states/ApiState.tsx)
 * @param user - The current user instance (see ../states/UserState.tsx)
 * @param userSettings - The current user settings (see ../states/SettingsState.tsx)
 * @param globalSettings - The global settings (see ../states/SettingsState.tsx)
 * @param navigate - The navigation function (see react-router-dom)
 * @param theme - The current Mantine theme
 * @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
 * @param context - Any additional context data which may be passed to the plugin
 */
export type InvenTreeContext = {
  api: AxiosInstance;
  queryClient: QueryClient;
  user: UserStateProps;
  userSettings: SettingsStateProps;
  globalSettings: SettingsStateProps;
  host: string;
  navigate: NavigateFunction;
  theme: MantineTheme;
  colorScheme: MantineColorScheme;
  context?: any;
};

export const useInvenTreeContext = () => {
  const host = useLocalState((s) => s.host);
  const navigate = useNavigate();
  const user = useUserState();
  const { colorScheme } = useMantineColorScheme();
  const theme = useMantineTheme();
  const globalSettings = useGlobalSettingsState();
  const userSettings = useUserSettingsState();

  const contextData = useMemo<InvenTreeContext>(() => {
    return {
      user: user,
      host: host,
      api: api,
      queryClient: queryClient,
      navigate: navigate,
      globalSettings: globalSettings,
      userSettings: userSettings,
      theme: theme,
      colorScheme: colorScheme
    };
  }, [
    user,
    host,
    api,
    queryClient,
    navigate,
    globalSettings,
    userSettings,
    theme,
    colorScheme
  ]);

  return contextData;
};

This context data can be used to provide additional information to the rendering functions, and can be used to dynamically render content based on the current state of the system.

Additional Context

Note that additional context can be passed to the rendering functions by adding additional key-value pairs to the context field in the UIFeature return type (provided by the backend via the API). This field is optional, and can be used at the discretion of the plugin developer.

File Distribution

When distributing a custom UI plugin, the plugin should include the necessary frontend code to render the custom content. This frontend code should be included in the plugin package, and should be made available to the InvenTree frontend when the plugin is installed.

The simplest (and recommended) way to achieve this is to distribute the compiled javascript files with the plugin package, in a top-level static directory. This directory will be automatically collected by InvenTree when the plugin is installed, and the files will be copied to the appropriate location.

Read more about static plugin files for more information.

Sample Plugin

A (very simple) sample plugin which implements custom user interface functionality is provided in the InvenTree source code, which provides a full working example of how to implement custom user interface functionality.

A sample plugin which demonstrates user interface integrations.

Source code in src/backend/InvenTree/plugin/samples/integration/user_interface_sample.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlugin):
    """A sample plugin which demonstrates user interface integrations."""

    NAME = 'SampleUI'
    SLUG = 'sampleui'
    TITLE = 'Sample User Interface Plugin'
    DESCRIPTION = 'A sample plugin which demonstrates user interface integrations'
    VERSION = '2.0'

    ADMIN_SOURCE = 'ui_settings.js'

    SETTINGS = {
        'ENABLE_PART_PANELS': {
            'name': _('Enable Part Panels'),
            'description': _('Enable custom panels for Part views'),
            'default': True,
            'validator': bool,
        },
        'ENABLE_PURCHASE_ORDER_PANELS': {
            'name': _('Enable Purchase Order Panels'),
            'description': _('Enable custom panels for Purchase Order views'),
            'default': False,
            'validator': bool,
        },
        'ENABLE_BROKEN_PANELS': {
            'name': _('Enable Broken Panels'),
            'description': _('Enable broken panels for testing'),
            'default': True,
            'validator': bool,
        },
        'ENABLE_DYNAMIC_PANEL': {
            'name': _('Enable Dynamic Panel'),
            'description': _('Enable dynamic panels for testing'),
            'default': True,
            'validator': bool,
        },
    }

    def get_ui_panels(self, request, context, **kwargs):
        """Return a list of custom panels to be injected into the UI."""
        panels = []
        context = context or {}

        # First, add a custom panel which will appear on every type of page
        # This panel will contain a simple message

        target_model = context.get('target_model', None)
        target_id = context.get('target_id', None)

        # A broken panel which tries to load a non-existent JS file
        if target_id is not None and self.get_setting('ENABLE_BROKEN_PANElS'):
            panels.append({
                'key': 'broken-panel',
                'title': 'Broken Panel',
                'source': '/this/does/not/exist.js',
            })

        # A dynamic panel which will be injected into the UI (loaded from external file)
        # Note that we additionally provide some "context" data to the front-end render function
        if self.get_setting('ENABLE_DYNAMIC_PANEL'):
            panels.append({
                'key': 'dynamic-panel',
                'title': 'Dynamic Panel',
                'source': self.plugin_static_file('sample_panel.js'),
                'icon': 'part',
                'context': {
                    'version': INVENTREE_SW_VERSION,
                    'plugin_version': self.VERSION,
                    'random': random.randint(1, 100),
                    'time': time.time(),
                },
            })

        # Next, add a custom panel which will appear on the 'part' page
        # Note that this content is rendered from a template file,
        # using the django templating system
        if self.get_setting('ENABLE_PART_PANELS') and target_model == 'part':
            try:
                part = Part.objects.get(pk=target_id)
            except (Part.DoesNotExist, ValueError):
                part = None

            panels.append({
                'key': 'part-panel',
                'title': _('Part Panel'),
                'source': self.plugin_static_file('sample_panel.js:renderPartPanel'),
                'icon': 'part',
                'context': {'part_name': part.name if part else ''},
            })

        # Next, add a custom panel which will appear on the 'purchaseorder' page
        if target_model == 'purchaseorder' and self.get_setting(
            'ENABLE_PURCHASE_ORDER_PANELS'
        ):
            panels.append({
                'key': 'purchase_order_panel',
                'title': 'Purchase Order Panel',
                'source': self.plugin_static_file('sample_panel.js:renderPoPanel'),
            })

        # Admin panel - only visible to admin users
        if request.user.is_superuser:
            panels.append({
                'key': 'admin-panel',
                'title': 'Admin Panel',
                'source': self.plugin_static_file(
                    'sample_panel.js:renderAdminOnlyPanel'
                ),
            })

        return panels

    def get_ui_dashboard_items(self, request, context, **kwargs):
        """Return a list of custom dashboard items."""
        items = [
            {
                'key': 'broken-dashboard-item',
                'title': _('Broken Dashboard Item'),
                'description': _(
                    'This is a broken dashboard item - it will not render!'
                ),
                'source': '/this/does/not/exist.js',
            },
            {
                'key': 'sample-dashboard-item',
                'title': _('Sample Dashboard Item'),
                'description': _(
                    'This is a sample dashboard item. It renders a simple string of HTML content.'
                ),
                'source': self.plugin_static_file('sample_dashboard_item.js'),
            },
            {
                'key': 'dynamic-dashboard-item',
                'title': _('Context Dashboard Item'),
                'description': 'A dashboard item which passes context data from the server',
                'source': self.plugin_static_file(
                    'sample_dashboard_item.js:renderContextItem'
                ),
                'context': {'foo': 'bar', 'hello': 'world'},
                'options': {'width': 3, 'height': 2},
            },
        ]

        # Admin item - only visible to users with superuser access
        if request.user.is_superuser:
            items.append({
                'key': 'admin-dashboard-item',
                'title': _('Admin Dashboard Item'),
                'description': _('This is an admin-only dashboard item.'),
                'source': self.plugin_static_file('admin_dashboard_item.js'),
                'options': {'width': 4, 'height': 2},
                'context': {'secret-key': 'this-is-a-secret'},
            })

        return items

    def get_ui_template_editors(self, request, context, **kwargs):
        """Return a list of custom template editors."""
        # If the context is a label template, return a custom template editor
        if context.get('template_type') == 'labeltemplate':
            return [
                {
                    'key': 'sample-template-editor',
                    'title': 'Sample Template Editor',
                    'icon': 'keywords',
                    'source': self.plugin_static_file(
                        'sample_template.js:getTemplateEditor'
                    ),
                }
            ]

        return []

    def get_ui_template_previews(self, request, context, **kwargs):
        """Return a list of custom template previews."""
        return [
            {
                'key': 'sample-template-preview',
                'title': 'Sample Template Preview',
                'icon': 'category',
                'source': self.plugin_static_file(
                    'sample_preview.js:getTemplatePreview'
                ),
            }
        ]

    def get_admin_context(self) -> dict:
        """Return custom context data which can be rendered in the admin panel."""
        return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}

More Examples

Some more complex examples of user interface plugins can be found on the InvenTree GitHub repository:

Consistent Theming

When developing a custom UI plugin for InvenTree, the plugin should aim to match the existing InvenTree theme as closely as possible. This will help to ensure that the custom content fits seamlessly into the existing user interface.

To achieve this, we strongly recommend that you use the same framework as the InvenTree frontend - which is built using React on top of the Mantine UI component library.

Mantine

The Mantine UI component library is used throughout the InvenTree frontend, and provides a consistent look and feel to the user interface. By using Mantine components in your custom UI plugin, you can ensure that your custom content fits seamlessly into the existing InvenTree theme.

InvenTree Component Library

We are working to develop and distribute a library of custom InvenTree components which can be used to build custom UI plugins. This library will be made available to plugin developers in the near future.

Examples

Refer to some of the existing InvenTree plugins linked above for examples of building custom UI plugins using the Mantine component library for seamless integration.