New in Dash 3.0
Dash provides a system of hooks for creating installable plugins that can extend the functionality of Dash apps and that can be used across multiple apps. You can use hooks to add to an app’s layout, add callbacks, routes, or error handlers.
This tutorial teaches the main features of Dash hooks.
In this tutorial, you’ll build a plugin that displays a notification when a callback error happens in a Dash app. The notification will explain what type of errors will be shown the first time the app loads. It will also include a button so it can be closed by the app user. It will reappear each time a callback error happens.
In this tutorial, we’ll build a reusable module. To create the project structure:
pyproject.toml.callback_error_plugin.callback_error_plugin subfolder, create a file called __init__.py.callback_error_plugin/
└── __init__.py
pyproject.toml
<img>
The pyproject.toml file is for project metadata, while callback_error_plugin is where the main logic of the module will live in the __init__.py file.
You could also split the module into multiple files, with __init__.py as the entry point and a new file callback_error_plugin.py for the main logic.
__init__.pyOpen the __init__.py file and import html, hooks, set_props, Input, and Output from dash to use later. Then, add the component that will be displayed when an error happens. Use dash.html components to create a simple banner that will be displayed when there’s a callback error.
```python
from dash import html, hooks, set_props, Input, Output
def generate_error_notification():
return [
html.Div(
[
html.Div(
[
html.Span(
“Callback errors will display here.”,
id=”error-text”,
),
html.Button(
“×”,
id=”dismiss-button”,
style={
“position”: “absolute”,
“top”: “5px”,
“right”: “10px”,
},
),
],
style={
“padding”: “15px”,
“border”: “1px solid #f5c6cb”,
},
)
],
id=”callback-error-banner-wrapper”,
)
]
```
Create a function add_error_notifications that accepts one argument error_text. This function will be called by apps that use the plugin’s functionality. App developers will be able to pass custom error text to it.
python
def add_error_notifications(error_text="There was an error"):
In the newly created add_error_notifications function, use the hooks.layout decorator to add the component to the app layout.
python
@hooks.layout()
def update_layout(layout):
return generate_error_notification() + (layout if isinstance(layout, list) else [layout])
In the add_error_notifications function, use the hooks.callback decorator to add a callback that handles dismissing the notification.
python
@hooks.callback(
Output("callback-error-banner-wrapper", "style"),
Input("dismiss-button", "n_clicks"),
prevent_initial_call=True,
)
def hide_banner(n_clicks):
if n_clicks:
return dict(display="none")
In the add_error_notifications function, use the hooks.error decorator to add a callback error handler. This error handler function unhides the banner and also update its text to show the actual error that occurred:
python
@hooks.error()
def on_error(err):
set_props("callback-error-banner-wrapper", dict(style=dict(display="block")))
set_props("error-text", dict(children=f"{error_text}: {err}"))
View the full code
from dash import html, hooks, set_props, Input, Output
def generate_error_notification():
return [
html.Div(
[
html.Div(
[
html.Span(
"Callback errors will display here.",
id="error-text",
),
html.Button(
"×",
id="dismiss-button",
style={
"position": "absolute",
"top": "5px",
"right": "10px",
},
),
],
style={
"padding": "15px",
"border": "1px solid #f5c6cb",
},
)
],
id="callback-error-banner-wrapper",
)
]
def add_error_notifications(error_text="There was an error"):
@hooks.layout()
def update_layout(layout):
return generate_error_notification() + (
layout if isinstance(layout, list) else [layout]
)
@hooks.callback(
Output("callback-error-banner-wrapper", "style"),
Input("dismiss-button", "n_clicks"),
prevent_initial_call=True,
)
def hide_banner(n_clicks):
if n_clicks:
return dict(display="none")
@hooks.error()
def on_error(err):
set_props("callback-error-banner-wrapper", dict(style=dict(display="block")))
set_props("error-text", dict(children=f"{error_text}: {err}"))
pyproject.toml FileAdd the following to the pyproject.toml file. Information in pyproject.toml is used when the package is built (the next step).
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "callback_error_plugin"
version = "0.0.1"
dependencies = [
"dash>=3.0.3",
]
[tool.setuptools]
packages = ["callback_error_plugin"]
To build an installable plugin, use build.
In a virtual environment:
pip install build
To build the plugin, run:
python -m build
A Python wheel and source distribution are generated in a dist directory alongside the plugin code.
dash>=3.0.3.pip install <path-to-generated-dist>.Create an app with the following code in a file called app.py to use with the new plugin. Note, the app imports add_error_notifications from the plugin package and calls it with a custom error message:
```python
from dash import dcc, html, callback, Input, Output, Dash
from callback_error_plugin import add_error_notifications
add_error_notifications(“Here is the error message”)
app = Dash()
app.layout = html.Div([
dcc.Input(id=’input-number’, type=’number’, value=1),
html.Div(id=’output-div’)
])
@callback(
Output(‘output-div’, ‘children’),
Input(‘input-number’, ‘value’)
)
def update_output(value):
result = 10 / value
return f’The result is {result}’
if name == ‘main’:
app.run(debug=True)
``
5. Run the app withpython app.pyand try it. This example app will throw a division by zero error when0` is entered in the input.
<img>.
Tip: You can also publish the built package to PyPI, the official repository of packages for Python. See the Python Packaging User Guide for details on uploading the generated wheel and source distributions.
Dash’s hooks system supports the following hooks:
Use dash.hooks.layout as a function decorator to update an app’s layout before the Dash app is served. The decorated function receives the existing layout as its first argument. What’s returned from the decorated function will be the app’s new layout.
Here’s an example that adds a new html.Div to an existing layout.
from dash import hooks, html
@hooks.layout()
def update_layout(layout):
return [html.Div("New layout content")] + layout
Use the hooks.callback decorator to add callbacks to an app. It accepts the same parameters as app.callback and dash.callback.
Here’s an example of adding a callback using hooks.callback. This callback outputs a message when a button is clicked.
from dash import hooks, Input, Output
@hooks.callback(
Output("message-container", "children"),
Input("app-button", "n_clicks"),
prevent_initial_call=True,
)
def output_message(n_clicks):
if n_clicks:
return "Displaying a message in the app on click"
Callbacks added with hooks.callback can reference components added with hooks.layout. See the “Example: Creating a Plugin” section for an example of using these hooks together.
Hiding Callbacks from Devtools
New in Dash 3.3
We recommend setting hidden=True on a callback to hide it from the callback graph in the dev tools UI. This allows plugin users to clearly see their own app’s callbacks when debugging, without the plugin’s internal callbacks getting in the way.
from dash import hooks, Input, Output
@hooks.callback(
Output("message-container", "children"),
Input("app-button", "n_clicks"),
prevent_initial_call=True,
hidden=True
)
def output_message(n_clicks):
if n_clicks:
return "Displaying a message in the app on click"
Use the hooks.route decorator to add Flask routes to an app. The function that hooks.route decorates is the view function for the route.
The decorator has the following arguments for configuring the route:
methods: a sequence of methods allowed on the route. Default is ("GET",)name: a name for the route. If none is provided, the name of the decorated function is used. The route name is automatically prefixed with routes_pathname_prefix.The following example serves some JSON data at /get-data:
from flask import jsonify
from dash import hooks
@hooks.route(methods=("GET",), name="get-data")
def new_route():
data = {
"status": "success"
}
return jsonify(data)
Use hooks.error as a function decorator to add a callback error handler. The decorated function will be set as the error handler for any app that uses the plugin. The decorated function will receive the error that occurs in a callback as an argument, err.
from dash import hooks
@hooks.error()
def on_error(err):
print(f"Printing the error to the console: {err}")
See the callback error handlers page for more details on creating callback error handlers.
Use hooks.setup as a function decorator to get a reference to an app’s instantiated dash.Dash app object. This can be used to update properties of the app.
Here’s an example that adds text to the app title:
from dash import hooks
@hooks.setup()
def setup(app_object):
app_object.title = f"{app_object.title} | Appending to the app title"
See the app object section in the reference docs for a list of properties that can be set on the app object.
hooks.indexhooks.index allows you to access a Dash app’s HTML and alter it. Use hooks.index as a decorator. The decorated function will receive the app’s index, a string, as its only argument. In this example, an HTML element is inserted just after the start of the <body> tag.
from dash import hooks
@hooks.index()
def update_index(app_index):
body = "<body>"
body_idx = app_index.find(body) + len(body)
updated_app_index = app_index[:body_idx] + '<div>Added content<div>' + app_index[body_idx:]
return updated_app_index
Use the custom_data hook as a decorator to make custom data available to callback_context within an app’s callbacks.
from dash import hooks
@hooks.custom_data("custom_data_name")
def custom_data_func(_ctx):
# The current callback context (here as the parameter `_ctx`) is available
# And could be used when generating custom data to return from this function
return "some custom data"
Within a Dash app, this value can be accessed from dash.callback_context:
from dash import dash, callback, Input, Output
@callback(
Output('cities-radio', 'value'),
Input('cities-radio', 'options'))
def set_cities_value(available_options):
custom_value = dash.callback_context.custom_data["custom_data_name"] # will return "some custom data"
...
Use hooks.devtool to add custom components to the Dash dev tools UI. Components added with this hook are rendered in the dev tools panel when dev_tools_ui=True is set on the Dash app (the default in debug mode).
hooks.devtool accepts the following arguments:
namespace: the component’s namespace. For example, "dash_html_components".component_type: the component type. For example, "Div", "Button", and "Dropdown".props: a dict of component props, or a callable that returns props. If a callable is provided, it will be called before sending the component to the frontend.position: where to render the component in the dev tools UI. Options are "left" or "right". Default is "right". "left" works best for toggle buttons, while "right" works for indicators, for example, the existing server status indicator in dev tools.The following example adds a simple component to the dev tools UI:
from dash import hooks
hooks.devtool(
namespace="dash_html_components",
component_type="Div",
props={"children": "Custom plugin dev tools panel"}
)
To use the component in callbacks, give it an id and use hooks.callback with optional=True on the callback:
from dash import hooks, Input, Output
hooks.devtool(
namespace="dash_html_components",
component_type="Div",
props={
"children": [
{
"namespace": "dash_html_components",
"type": "Button",
"props": {
"id": "plugin-button",
"children": "Click me"
}
},
{
"namespace": "dash_html_components",
"type": "Div",
"props": {
"id": "plugin-output",
"children": "Output will appear here"
}
}
]
}
)
@hooks.callback(
Output("plugin-output", "children"),
Input("plugin-button", "n_clicks"),
# `optional=True` is required because devtools are outside the regular layout
optional=True,
prevent_initial_call=True
)
def handle_plugin_button(n_clicks):
return f"Button clicked {n_clicks} times"
You can get the values for namespace, component_type and props by calling to_plotly_json() on a Dash component:
component = dash.html.Div("Custom plugin dev tools panel")
component.to_plotly_json()
# Output of component.to_plotly_json()
{'props': {'children': 'Custom plugin dev tools panel'}, 'type': 'Div', 'namespace': 'dash_html_components'}
# Values from to_plotly_json() passed to `hooks.devtool`. Note: `type` on the `to_plotly_json` output corresponds to the `component_type` argument on `hooks.devtool`:
hooks.devtool(
namespace=component.to_plotly_json()['namespace'],
component_type=component.to_plotly_json()['type'],
props=component.to_plotly_json()['props'],
)
As well as rendering Dash components, hooks.devtool can render custom React components.
To render a React component in the dev tools UI, the component needs to be available in the window namespace.
Here’s a React component in custom_devtools.js:
window.CustomDevToolsLib = {
SimpleComponent: function(props) {
return window.React.createElement('div', { style: { padding: '10px' } }, [
window.React.createElement('h4', { key: 'title' }, props.title || 'Custom Component'),
window.React.createElement('p', { key: 'msg' }, props.message || 'Hello!')
]);
}
};
Which is then made available to the Dash application by adding it with hooks.script and then adding it to the dev tools UI with hooks.devtool:
from dash import hooks
hooks.script([{
"dev_package_path": "custom_devtools.js", # Relative path within package
"namespace": "custom_devtools_plugin",
"dev_only": True # Only load in debug mode
}])
hooks.devtool(
namespace="CustomDevToolsLib",
component_type="SimpleComponent",
props={
"title": "My Custom Component",
"message": "This is a custom React component in devtools!"
},
position="left"
)
New in Dash 3.3.0
When creating custom React components, use the devtool component API to apply styles that are consistent with the styling of built-in dev tools UI buttons.
useDevtool() - Manages which popup is open.
const { popup, setPopup } = window.dash_component_api.devtool.useDevtool();
const isOpen = popup === 'my-popup-id';
setPopup('my-popup-id'); // Open this popup
setPopup(''); // Close popup by setting it to ''
useDevtoolMenuButtonClassName(popupId) - Returns a CSS class string that styles a button to match built-in devtools buttons, ensuring that it appears as toggled correctly based on the popup state.
const className = window.dash_component_api.devtool.useDevtoolMenuButtonClassName('my-popup-id');
// Use it on a button and it will automatically show toggled state when popup is open
window.React.createElement('button', { className: className }, 'My Component')
If you are using multiple hooks of the same type, you can configure the order they run in using priority. Higher numbers run first. If no order is configured, the order is not guaranteed.
...
@hooks.layout(priority=2)
def update_layout(layout):
The dash.hooks module also allows you to directly add to an app’s clientside callbacks, scripts and CSS stylesheets.
You can add clientside callbacks to Dash apps using hooks.clientside_callback. For example, here’s the earlier callback example rewritten as a clientside callback:
from dash import hooks, Input, Output
hooks.clientside_callback(
"""
function(n_clicks) {
if (n_clicks) {
return {"display": "none"};
}
return {};
}
""",
Output("callback-error-banner-wrapper", "style"),
Input("dismiss-button", "n_clicks"),
prevent_initial_call=True
)
hooks.clientside_callback has the same functionality as app.clientside_callback.
Use hooks.script to add JavaScript scripts to apps. hooks.script takes a list of dicts, where each dict specifies one script to add. Scripts can be loaded from external URLs or package paths. The following example adds an external script:
from dash import hooks
hooks.script([{'external_url': 'https://www.google-analytics.com/analytics.js', 'external_only': True}])
Script Options
Each script dictionary can include the following fields:
external_url: URL of the external scriptexternal_only: If True, always use the external URL regardless of the app’s serve_locally settingrelative_package_path: Path to the script relative to the package (requires __version__ and namespace)dev_package_path: Path to development version relative to the package (requires __version__ and namespace)namespace: Namespace for plugin scripts (required when using relative_package_path or dev_package_path)dev_only: If True, only load in debug mode (useful for plugins that add to the dev tools UI)Examples
Always use external URL:
hooks.script([{
'external_url': 'https://cdn.example.com/library.js',
'external_only': True
}])
Use package path:
__version__ = "1.0.0"
hooks.script([{
'external_url': 'https://cdn.example.com/my-plugin.js',
'relative_package_path': 'plugin_resources/my-plugin.js',
'namespace': 'my_plugin_package'
}])
Use hooks.stylesheet to add stylesheets to apps. hooks.stylesheet takes a list of dicts, where each dict specifies one stylesheet to add. Stylesheets can be loaded from external URLs or package paths. The following example adds an external stylesheet:
from dash import hooks
hooks.stylesheet([{'external_url': 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css', 'external_only': True}])
Stylesheet Options
Each stylesheet dictionary can include the following fields:
external_url: URL of the external stylesheetexternal_only: If True, always use the external URL regardless of the app’s serve_locally settingrelative_package_path: Path to the stylesheet relative to the package (requires __version__ and namespace)dev_package_path: Path to development version relative to the package (requires __version__ and namespace)namespace: Namespace for plugin stylesheets (required when using relative_package_path or dev_package_path)dev_only: If True, only load in debug mode (useful for plugins that add to the dev tools UI)Examples
Always use external URL:
hooks.stylesheet([{
'external_url': 'https://cdn.example.com/styles.css',
'external_only': True
}])
Use package path:
__version__ = "1.0.0"
hooks.stylesheet([{
'external_url': 'https://cdn.example.com/my-plugin.css',
'relative_package_path': 'plugin_resources/my-plugin.css',
'namespace': 'my_plugin_package'
}])
The earlier tutorial demonstrated creating a plugin that could be used with a Dash app by importing it to register the plugin’s hooks. It’s also possible to create a plugin where hooks are automatically discovered and registered for a Dash app once the plugin is installed.
For automatic discovery, the plugin needs to define a dash_hooks entry point. When a plugin that defines a dash_hooks entry point is installed in an environment where a Dash app is run, Dash will run the hooks defined by the plugin before serving the Dash app.
Here’s an example of a pyproject.toml file for the plugin from the earlier tutorial:
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "callback_error_plugin"
version = "0.0.2"
dependencies = [
"dash>=3.0.3",
]
[project.entry-points."dash_hooks"]
callback_error_plugin = "callback_error_plugin"
[tool.setuptools]
packages = ["callback_error_plugin"]
With the entry point configured, the code in the plugin’s __init__.py file would look like this, with the calls to hooks defined outside of the function:
from dash import html, hooks, set_props, Input, Output
def generate_error_notification():
return [
html.Div(
[
html.Div(
[
html.Span(
"Callback errors will display here.",
id="error-text",
),
html.Button(
"×",
id="dismiss-button",
style={
"position": "absolute",
"top": "5px",
"right": "10px",
},
),
],
style={
"padding": "15px",
"border": "1px solid #f5c6cb",
},
)
],
id="callback-error-banner-wrapper",
)
]
@hooks.layout()
def update_layout(layout):
return generate_error_notification() + (
layout if isinstance(layout, list) else [layout]
)
@hooks.callback(
Output("callback-error-banner-wrapper", "style"),
Input("dismiss-button", "n_clicks"),
prevent_initial_call=True,
)
def hide_banner(n_clicks):
if n_clicks:
return dict(display="none")
@hooks.error()
def on_error(err):
set_props("callback-error-banner-wrapper", dict(style=dict(display="block")))
set_props("error-text", dict(children=f"The error is: {err}"))
If multiple plugins in the environment where a Dash app is run have a dash_hooks entry point defined, each plugin’s defined hooks will be run before serving the Dash app.