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__.py
Open 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. Give this hook a priority of 1
. This ensures that layout is updated first, adding the new component. The new component can then be used in a callback hook in the next step.
python
@hooks.layout(
priority=1
)
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(priority=1)
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 with
python app.pyand try it. This example app will throw a division by zero error when
0` 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 can be configured in the same way 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"
You’ll need to also have a hooks.layout
hook to use hooks.callback
. See the “Example: Creating a Plugin” section for an example of using these hooks together.
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.index
hooks.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 + 1]
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"
...
If you are using multiple hooks, you can configure the order they run in using priority
, or configure one hook to be the final hook to run by using final=True
. If no order is configured, the order is not guaranteed.
In the following example, the route hook will run first, then the layout hook, and the callback hook will run last.
from dash import hooks, html, Input, Output
from flask import jsonify
@hooks.callback(
Output("message-container", "children"),
Input("app-button", "n_clicks"),
prevent_initial_call=True,
final=True
)
def output_message(n_clicks):
if n_clicks:
return "Displaying a message in the app on click"
@hooks.layout(priority=2)
def update_layout(layout):
return [html.Div("New layout content")] + layout
@hooks.route(methods=("GET",), name="get-data", priority=1)
def new_route():
data = {
"status": "success"
}
return jsonify(data)
Note: There can be only one
final
hook across all packages that usedash.hooks
in an environment. If multiple hooks use it, users will see a “dash.exceptions.HookError: Final hook already present
” error when running a Dash app.
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 external JavaScript scripts to apps. hooks.script
takes a list of dict
s where each dict
is one external script to add. The following example adds a Google Analytics URL. external_url
is the location of the script to add to the app. And external_only
specifies there is no local version of the script. This ensures it works with apps that have serve_locally=True
, the default in Dash.
from dash import hooks
hooks.script([{'external_url': 'https://www.google-analytics.com/analytics.js', 'external_only': True}])
Adding the above using hooks.script
is equivalent to the app developer adding a script with external_scripts
on the dash.Dash
constructor:
import dash
app = dash.Dash(external_scripts=['https://www.google-analytics.com/analytics.js'])
Use hooks.stylesheet
to add external stylesheets to apps. hooks.stylesheet
takes a list of dict
s where each dict
is one external stylesheet to add. The following example adds Bootstrap CSS. external_url
is the location of the script to add to the app. And external_only
specifies there is no local version of the stylesheet. This ensures it works with apps that have serve_locally=True
, the default in Dash.
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}])
Adding the above using hooks.stylesheet
is equivalent to the app developer adding a stylesheet with external_stylesheets
on the dash.Dash
constructor:
import dash
app = dash.Dash(external_stylesheets=['https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css'])
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(priority=1)
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.