Multi-Page Apps and URL Support

Dash renders web applications as a “single-page app”. When using dcc.Link, the application does not completely reload when navigating, making browsing very fast.
Using Dash you can build multi-page apps using dcc.Location and dcc.Link components and callbacks.

Dash Pages uses these components and abstracts away the callback logic required for URL routing, making it easy to get up and running with a multi-page app. If you want to build a multi-page app without Pages, see the Multi Page Apps without Pages section below.

Dash Pages

Dash Pages is new in Dash 2.5.
Check your version with: print(dash.__version__)

This feature was developed in the open with collaboration from the Dash Community. Many thanks to everyone! View the original discussion & announcement.

Dash Pages is available from Dash version 2.5.0. It implements features to simplify creating a multi-page app, handling URL routing and offering an easy way to structure and define the pages in your app.

There are three basic steps for creating a multi-page app with Dash Pages:

  1. Create individual .py files for each page in your app, and put them in a /pages directory.

  2. In each of these page files:

    • Add a dash.register_page(__name__), which tells Dash that this is a page in your app.
    • Define the page’s content within a variable called layout or a function called layout that returns the content.
  3. In your main app file, app.py:
    - When declaring your app, set use_pages to True: app = Dash(__name__, use_pages=True)
    - Add dash.page_container in your app layout where you want the page content to be displayed when a user visits one of the app’s page paths.

Example: Simple Multi-Page App with Pages

Here is what a three page app structure looks like with Dash Pages:

- app.py
- pages
   |-- analytics.py
   |-- home.py
   |-- archive.py

It has the main app.py file which is the entry point to our multi-page app (and in which we include dash.page_container) and three pages in our pages directory.

pages/analytics.py:

import dash
from dash import html, dcc, callback, Input, Output

dash.register_page(__name__)

layout = html.Div(children=[
    html.H1(children='This is our Analytics page'),
    html.Div([
        "Select a city: ",
        dcc.RadioItems(['New York City', 'Montreal','San Francisco'],
        'Montreal',
        id='analytics-input')
    ]),
    html.Br(),
    html.Div(id='analytics-output'),
])


@callback(
    Output(component_id='analytics-output', component_property='children'),
    Input(component_id='analytics-input', component_property='value')
)
def update_city_selected(input_value):
    return f'You selected: {input_value}'

pages/home.py:

import dash
from dash import html, dcc

dash.register_page(__name__, path='/')

layout = html.Div(children=[
    html.H1(children='This is our Home page'),

    html.Div(children='''
        This is our Home page content.
    '''),

])

pages/archive.py:

import dash
from dash import html, dcc

dash.register_page(__name__)

layout = html.Div(children=[
    html.H1(children='This is our Archive page'),

    html.Div(children='''
        This is our Archive page content.
    '''),

])

app.py:

from dash import Dash, html, dcc
import dash

app = Dash(__name__, use_pages=True)

app.layout = html.Div([
    html.H1('Multi-page app with Dash Pages'),

    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["relative_path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),

    dash.page_container
])

if __name__ == '__main__':
    app.run_server(debug=True)

Simple multi-page app

Notes:

Layout

In the above example, we’ve defined the layout in each page using a variable called layout. For example, in home.py above:


layout = html.Div(children=[
    html.H1(children='This is our Home page'),

    html.Div(children='''
        This is our Home page content.
    '''),

])

You can also use a function called layout that returns your page content:


def layout():
    return html.Div(children=[
    html.H1(children='This is our Home page'),

    html.Div(children='''
        This is our Home page content.
    '''),

])

Page layouts must be defined with a variable or function called layout. When creating an app with Pages, only use app.layout in your main app.py file.

dash.register_page

Calling dash.register_page within a file is how Dash knows to include the file as a page in your multi-page app.

As we’ve seen, it can be called with just the module name:

dash.register_page(__name__)

In this case, Dash generates the path the page is for, its title, and the link name based on the module name.

The title is the HTML <title>. The name is the key for this page in the Dash Registry and can be used when creating links for pages. The path is the URL pathname of the page.

So, if we have a file called analytics.py, the page’s path is /analytics, the title is Analytics, and the link name is Analytics.

We can also specify these if we don’t want them to be autogenerated based on the module name, as we did in the example above with our home page.

Setting a path, title, and link name:

pages/analytics.py

dash.register_page(
    __name__,
    path='/analytics-dashboard',
    title='Our Analytics Dashboard',
    name='Our Analytics Dashboard'
)

See the Reference for dash.register_page section below for a detailed list of properties.

Dash Page Registry

Any pages that call dash.register_page are added to a page registry for your app.

The page registry is an OrderedDict called dash.page_registry. Each registry entry has information for a page, including property values set when dash.register_page was called, and values inferred by Dash. As with any dict, you can access and use its data in your code.

Here we access the path of our analytics and use it in a dcc.Link in app.py:

html.Div(dcc.Link('Dashboard', href=dash.page_registry['pages.analytics']['path']))

To access dash.page_registry from within a file in the pages directory, you’ll need to use it within a function.

Here, we have two files within the pages directory: side_bar.py and topic_1.py. The topic_1 page imports a sidebar from side_bar.py. Note how the function within side_bar.py accesses dash.page_registry. If this wasn’t within a function, the app wouldn’t work because the dash.page_registry wouldn’t be ready when the page loads.

side_bar.py

import dash
from dash import html
import dash_bootstrap_components as dbc


def sidebar():
    return html.Div(
        dbc.Nav(
            [
                dbc.NavLink(
                    [
                        html.Div(page["name"], className="ms-2"),
                    ],
                    href=page["path"],
                    active="exact",
                )
                for page in dash.page_registry.values()
                if page["path"].startswith("/topic")
            ],
            vertical=True,
            pills=True,
            className="bg-light",
        )
    )

topic_1.py

from dash import html

import dash
import dash_bootstrap_components as dbc

from .side_bar import sidebar

dash.register_page(
    __name__,
    name="Topics",
    top_nav=True,
)


def layout():
    return dbc.Row(
        [dbc.Col(sidebar(), width=2), dbc.Col(html.Div("Topics Home Page"), width=10)]
    )

What the Dash Page Registry looks like for our initial example, Simple Multi-page App with Pages

OrderedDict([('pages.home',
              {'description': '',
               'image': None,
               'layout': Div([H1('This is our Home page'), Div('\n        This is our Home page content.\n    ')]),
               'module': 'pages.home',
               'name': 'Home',
               'order': 0,
               'path': '/',
               'path_template': None,
               'redirect_from': None,
               'relative_path': '/',
               'supplied_image': None,
               'supplied_layout': None,
               'supplied_name': None,
               'supplied_order': None,
               'supplied_path': '/',
               'supplied_title': None,
               'title': 'Home'}),
             ('pages.analytics',
              {'description': '',
               'image': None,
               'layout': Div([H1('This is our Analytics page'), Div(['Select a city: ', RadioItems(options=['New York City', 'Montreal', 'San Francisco'], value='Montreal', id='analytics-input')]), Br(None), Div(id='analytics-output')]),
               'module': 'pages.analytics',
               'name': 'Analytics',
               'order': None,
               'path': '/analytics',
               'path_template': None,
               'redirect_from': None,
               'relative_path': '/analytics',
               'supplied_image': None,
               'supplied_layout': None,
               'supplied_name': None,
               'supplied_order': None,
               'supplied_path': None,
               'supplied_title': None,
               'title': 'Analytics'}),
             ('pages.archive',
              {'description': '',
               'image': None,
               'layout': Div([H1('This is our Archive page'), Div('\n        This is our Archive page content.\n    ')]),
               'module': 'pages.archive',
               'name': 'Archive',
               'order': None,
               'path': '/archive',
               'path_template': None,
               'redirect_from': None,
               'relative_path': '/archive',
               'supplied_image': None,
               'supplied_layout': None,
               'supplied_name': None,
               'supplied_order': None,
               'supplied_path': None,
               'supplied_title': None,
               'title': 'Archive'})])

Dash Page Registry Order

By default, a page with a path defined as ‘/’ is added to the registry at index 0.
Other pages are then added in alphabetical order based on file name.

You can also specify the order of pages in dash.page_registry by setting the order property on each page:

pages/analytics.py:

dash.register_page(__name__, order=3)

If you set the order property on one or more pages, pages are added to the registry:
- In the order they are specified with the order property.
- In alphabetical order after that (for pages without the order property set.

Setting the order can be useful when you want to be able to loop through the links when creating a sidebar or header dynamically.

Default and Custom 404

If a user goes to a path that hasn’t been declared in one of your app’s pages, Pages shows a default ‘404 - Page not found message’ to the user.

This page can be customized. Place a file called not_found_404.py in your app’s pages directory, add dash.register_page(__name__) to the file, and define the content for the custom 404 within a layout variable or function:

from dash import html
import dash

dash.register_page(__name__)

layout = html.H1("This is our custom 404 content")

Variable Paths

You can capture dynamic variables in the path by using the path_template parameter. Specify dynamic parts of your URL by placing it within <variable_name>. variable_name will be the named keyword argument passed into your layout function. Values that the layout function receives from the URL are always of type str.

Example - Single Variable Path

import dash
from dash import html

dash.register_page(__name__, path_template="/report/<report_id>")


def layout(report_id=None):
    return html.Div(
        f"The user requested report ID: {report_id}."
    )

Using path variables

Example - Two Path Variables and Update Title & Description

The path variables can also be used to update the page’s title (what you see in the browser tab) and the page’s meta description (information used by search engines when indexing and displaying search results and also displayed in social media when sharing links; otherwise not visible). More information on these parameters can be found in the Reference for dash.register_page section below.

import dash

def title(asset_id=None, dept_id=None):
    return f"Asset Analysis: {asset_id} {dept_id}"


def description(asset_id=None, dept_id=None):
    return f"This is the AVN Industries Asset Analysis: {asset_id} in {dept_id}"


dash.register_page(
    __name__,
    path_template="/asset/<asset_id>/department/hello-<dept_id>",
    title=title,
    description=description,
)


def layout(asset_id=None, dept_id=None, **other_unknown_query_strings):
    return dash.html.Div(
        f"variables from pathname:  asset_id: {asset_id} dept_id: {dept_id}"
    )

Query Strings

Query string parameters in a URL can be captured by Pages.

Example - Single Query String Parameter

In this example, when the user goes to /archive?report_id=9, the value 9 is captured by the layout function and displayed on the page. Values that the layout function receives from the URL are always of type str.

import dash
from dash import html

dash.register_page(__name__)

def layout(report_id=None, **other_unknown_query_strings):
    return html.Div(
    children=[
        html.H1(children='This is our Archive page'),

        html.Div(children=f'''
            This is report: {report_id}.
        '''),

    ])

Example - Two Query String Parameters

In this example, when the user goes to /archive?report_id=9&department_id=55, the values 9 and 55 are captured by the layout function and displayed on the page.

import dash
from dash import html

dash.register_page(__name__)

def layout(report_id=None, department_id=None, **other_unknown_query_strings):
    return html.Div(
    children=[
        html.H1(children='This is our Archive page'),

        html.Div(children=f'''
            This is report: {report_id}.\n
            This is department: {department_id}.
        '''),

    ])

Redirects

If you change a page’s path, it’s best practice to define a redirect so users that go to old links don’t get a ‘404 – Page not found’. You can set additional paths to direct to a page using the redirects parameter. This takes a list of all paths that redirect to this page.

Here we have a page called archive. It is displayed when a user goes to /archive, /archive-2021, or /archive-2020

archive.py

import dash
from dash import html, dcc

dash.register_page(
    __name__,
    path="/archive",
    redirect_from=["/archive-2021", "/archive-2020"]
)


layout = html.Div(children=[
    html.H1(children='This is our Archive page'),

    html.Div(children='''
        This is our Archive page content.
    '''),

])

Meta Tags

Not sure what meta tags are? Check out this tutorial on meta tags and why you might want to use them.

Each page you add to your app has page meta tags stored for it: a title, image, and description.

The title is used as the page title in the browser, but together with the image and description, it is also often used by social media sites and chat clients to create a card to display when someone shares a link to a page.

You can set the values for these properties with title=, description=, image=:

dash.register_page(__name__, title='Custom Page Title', description='Custom Page Description', image='logo.png')

Image types of apng, avif, gif, jpeg, jpg, png, svg, and webp are supported.

The image value must be the name of a file inside the assets folder. To set the image to a file that is not in the assets folder, such as an image hosted externally on a CDN, change image= to image_url= and provide the URL.

If you don’t specify title, it is derived from the module name. If you don’t specify a description, it defaults to None. Lastly, if you don’t specify image, Pages checks for an image that meets one of these criteria (in order) and uses the first one it finds:

For example, placing a file analytics.png in the assets folder sets this file as the image for pages/analytics.py, because the first criterion is met.

A more complete example for setting meta tags with Pages might look like:

import dash
from dash import html, dcc


dash.register_page(
    __name__,
    title='Explore the archive',
    image='archive_image_2022.png',
    description='The archive page shows previously generated reports.'
)

layout = html.Div(children=[
    html.H1(children='This is our Archive page'),

    html.Div(children='''
        This is our Archive page content.
    '''),

])

The title and description properties can also be set as functions. If provided as functions, Pages calls these functions on page load and uses the values that they return.

Additional Keywords with Dash Page Registry

You can use additional custom key-value pairs when calling dash.register_page to add those to the Dash Page Registry.

For example, if you want to add information to a page about where its links appear on the home page, you could add a “location” keyword with a value of “sidebar”.

dash.register_page(__name__, location = "sidebar")

In your app.py page, in the sidebar, you can then loop through the pages that have that location set:

html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']}", href=page["path"]
                )
            )
            for page in dash.page_registry.values() if page["location"] == "sidebar"
        ]

When you add new pages with dash.register_page(__name__, location = "sidebar"), they’ll automatically be included in the sidebar.

Nested Pages

Dash Pages also recursively searches directories in the pages directory for additional app pages.
For example, if we add a reports (this name is arbitrary!) directory within pages, put two pages, summary_2020.py and summary_2021.py, in that directory, and call dash.register_page(__name__), they will be included in our app.

- app.py
- pages
    - reports
        |-- summary_2020.py
        |-- summary_2021.py
   |-- analytics.py
   |-- home.py
   |-- archive.py

pages/reports/summary_2020.py:

import dash
from dash import html, dcc

dash.register_page(
    __name__,
)


layout = html.Div(children=[
    html.H1(children='2020 Summary'),

    html.Div(children='''
        This is our page's content.
    '''),

])

As we haven’t set the path property, Pages will display this page when the user visits the app at the URL path /reports/summary-2020.

Changing the Default Pages Directory

By default, Pages checks for a directory called pages for your app files. You can changes this when declaring your Dash app:


app = dash.Dash(__name__, use_pages=True, pages_folder="my_apps")

Multiple Pages in One File

So far, we’ve built a multi-page app where we’ve declared each page in a separate .py file in our pages directory. It’s also possible to declare multiple pages within app.py.

To do this, we register the page within app.py and pass the layout directly to dash.register_page. In this example, we define two pages within our app.py file: a home page, and an analytics page. For module, the first argument, we give each of our pages a unique name (as these names are used as keys in the Dash Page Registry).

from dash import Dash, html, dcc, callback
import dash

app = Dash(__name__, use_pages=True)

dash.register_page("home",  path='/', layout=html.Div('Home Page'))
dash.register_page("analytics", layout=html.Div('Analytics'))

app.layout = html.Div([
    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["relative_path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),
    dash.page_container,
])


if __name__ == '__main__':
    app.run_server(debug=True)

Circular Imports

When using Pages, the file that declares Dash(__name__, use_pages=True) recursively imports all files within the pages folder. If any of those pages import a function from the Dash file (usually app.py), then you will get a circular import error.

For example, this will cause a circular import error:

app.py

import dash
from dash import html

app = dash.Dash(
    __name__,
    use_pages=True
)

app.layout = html.Div(
    dash.page_container
)


if __name__ == "__main__":
    app.run_server(debug=True)

analytics.py

from dash import Input, Output, html, dcc
from app import app

layout = html.Div([dcc.Input(id='input'), html.Div(id='output')])

@app.callback(Output('output', 'children'), Input('input', 'value'))
def update(value):
    return value

Running python app.py displays the error KeyError: 'pages.analytics'

Reference for dash.register_page

Assigns the variables to dash.page_registry as an OrderedDict
(ordered by order).

dash.page_registry is used by pages_plugin to set up the layouts as
a multi-page Dash app. This includes the URL routing callbacks
(using dcc.Location) and the HTML templates to include title,
meta description, and the meta description image.

dash.page_registry can also be used by Dash developers to create the
page navigation links or by template authors.


page_registry stores the original property that was passed in under
supplied_<property> and the coerced property under <property>.
For example, if this was called:

register_page(
    'pages.historical_outlook',
    name='Our historical view',
    custom_key='custom value'
)

Then this will appear in page_registry:

OrderedDict([
    (
        'pages.historical_outlook',
        dict(
            module='pages.historical_outlook',

            supplied_path=None,
            path='/historical-outlook',

            supplied_name='Our historical view',
            name='Our historical view',

            supplied_title=None,
            title='Our historical view'

            supplied_layout=None,
            layout=<function>,

            custom_key='custom value'
        )
    ),
])

Multi-Page Apps without Pages

Dash Pages (available in Dash 2.5 and later) is the easiest way to build a multi-page app in Dash. If you are using an earlier version of Dash 2.x, you can build a multi-page app using the following guide.

Dash Pages uses a dcc.Location callback under-the-hood as described in the method below. Dash Pages also automatically:
- Sets configurable title, description, and image meta tags using interpolate_index and clientside callbacks under-the-hood
- Sets configurable redirects using flask.redirect under-the-hood
- Sets configurable 404 content
- Sets validate_layout under-the-hood to avoid callback exceptions
See the community announcement for the original discussion of this feature.

The components dcc.Location and dcc.Link aid page navigation: dcc.Location represents the web browser address bar. You can access the current pathname in the user’s browser with dcc.Location‘s pathname property. dcc.Link updates the pathname in the browser.

In the following examples, we demonstrate using these components to build multi-page apps, without using Dash Pages.

Simple Example

from dash import Dash, dcc, html, callback, Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    # represents the browser address bar and doesn't render anything
    dcc.Location(id='url', refresh=False),

    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),

    # content will be rendered in this element
    html.Div(id='page-content')
])


@callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    return html.Div([
        html.H3(f'You are on page {pathname}')
    ])


if __name__ == '__main__':
    app.run_server(debug=True)

In this example, the callback display_page receives the current pathname (the last part of the URL) of the page. The callback simply displays the pathname on page but it could use the pathname to display different content.

The Link element updates the pathname of the browser without refreshing the page. If you used a html.A element instead, the pathname would update but the page would refresh.

Here is what this example running looks like. Note how clicking on the Link doesn’t refresh the page even though it updates the URL!

Example of a multi-page Dash app using the Location and Link components


Example With Different Pages

You can modify the previous example to display different pages depending on the URL:

from dash import Dash, dcc, html, Input, Output, callback

app = Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


index_page = html.Div([
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Dropdown(['LA', 'NYC', 'MTL'], 'LA', id='page-1-dropdown'),
    html.Div(id='page-1-content'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
    html.Br(),
    dcc.Link('Go back to home', href='/'),
])

@callback(Output('page-1-content', 'children'),
              [Input('page-1-dropdown', 'value')])
def page_1_dropdown(value):
    return f'You have selected {value}'


page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.RadioItems(['Orange', 'Blue', 'Red'], 'Orange', id='page-2-radios'),
    html.Div(id='page-2-content'),
    html.Br(),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go back to home', href='/')
])

@callback(Output('page-2-content', 'children'),
              [Input('page-2-radios', 'value')])
def page_2_radios(value):
    return f'You have selected {value}'


# Update the index
@callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == '/page-1':
        return page_1_layout
    elif pathname == '/page-2':
        return page_2_layout
    else:
        return index_page
    # You could also return a 404 "URL not found" page here

if __name__ == '__main__':
    app.run_server(debug=True)

Dash app with multiple pages

In this example, we’re displaying different layouts through the display_page function. A few notes:
- Each page can have interactive elements even though those elements may not be in the initial view. Dash handles these “dynamically generated” components gracefully: as they are rendered, they will trigger the callbacks with their initial values.
- Since we’re adding callbacks to elements that don’t exist in the app.layout, Dash will raise an exception to warn us that we might be doing something wrong. In this case, we’re adding the elements through a callback, so we can ignore the exception by setting suppress_callback_exceptions=True. It is also possible to do this without suppressing callback exceptions. See the example below for details.
- You can modify this example to import the different page’s layouts in different files.
- This Dash Userguide that you’re looking at is itself a multi-page Dash app, using these same principles.


Dynamically Create a Layout for Multi-Page App Validation

Dash applies validation to your callbacks, which performs checks such as validating the types of callback arguments and checking to see whether the
specified Input and Output components actually have the specified properties.

For full validation, all components within your callback must therefore appear
in the initial layout of your app, and you will see an error if they do not. However, in the case of more complex Dash apps that involve dynamic modification
of the layout (such as multi-page apps), not every component appearing in your
callbacks will be included in the initial layout.

New in Dash 1.12 You can set app.validation_layout to a “complete” layout that contains all the components you’ll use in any of the pages / sections. app.validation_layout must be a Dash component, not a function. Then set app.layout to just the index layout. In previous Dash versions there was a trick you could use to achieve the same result, checking flask.has_request_context inside a layout function - that will still work but is no longer recommended.

from dash import Dash, html, dcc, Input, Output, State, callback

app = Dash(__name__)

url_bar_and_content_div = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])

layout_index = html.Div([
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_1 = html.Div([
    html.H2('Page 1'),
    dcc.Input(id='input-1-state', type='text', value='Montreal'),
    dcc.Input(id='input-2-state', type='text', value='Canada'),
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Div(id='output-state'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_2 = html.Div([
    html.H2('Page 2'),
    dcc.Dropdown(['LA', 'NYC', 'MTL'], 'LA', id='page-2-dropdown'),
    html.Div(id='page-2-display-value'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
])

# index layout
app.layout = url_bar_and_content_div

# "complete" layout
app.validation_layout = html.Div([
    url_bar_and_content_div,
    layout_index,
    layout_page_1,
    layout_page_2,
])


# Index callbacks
@callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == "/page-1":
        return layout_page_1
    elif pathname == "/page-2":
        return layout_page_2
    else:
        return layout_index


# Page 1 callbacks
@callback(Output('output-state', 'children'),
              Input('submit-button', 'n_clicks'),
              State('input-1-state', 'value'),
              State('input-2-state', 'value'))
def update_output(n_clicks, input1, input2):
    return f'The Button has been pressed {n_clicks} times. \
            Input 1 is {input1} and Input 2 is {input2}'


# Page 2 callbacks
@callback(Output('page-2-display-value', 'children'),
              Input('page-2-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'


if __name__ == '__main__':
    app.run_server(debug=True)

Structuring a Multi-Page App

Earlier examples show each multi-page app created within a single Python file. For bigger apps, a structure with multiple files may make the app easier to manage.

One Page Per File

One way to structure a multi-page app is to have each page as a separate app imported in the main app (app.py). In the following example, we build our app with two pages pages/page1.py and pages/page2.py. More pages (for example, pages/page3.py) can easily be added to this structure.

File structure:

- app.py
- pages
   |-- __init__.py
   |-- page1.py
   |-- page2.py

pages/page1.py

from dash import dcc, html, Input, Output, callback

layout = html.Div([
    html.H3('Page 1'),
    dcc.Dropdown(
        {f'Page 1 - {i}': f'{i}' for i in ['New York City', 'Montreal', 'Los Angeles']},
        id='page-1-dropdown'
    ),
    html.Div(id='page-1-display-value'),
    dcc.Link('Go to Page 2', href='/page2')
])


@callback(
    Output('page-1-display-value', 'children'),
    Input('page-1-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'

pages/page2.py

from dash import dcc, html, Input, Output, callback

layout = html.Div([
    html.H3('Page 2'),
    dcc.Dropdown(
        {f'Page 2 - {i}': f'{i}' for i in ['London', 'Berlin', 'Paris']},
        id='page-2-dropdown'
    ),
    html.Div(id='page-2-display-value'),
    dcc.Link('Go to Page 1', href='/page1')
])


@callback(
    Output('page-2-display-value', 'children'),
    Input('page-2-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'

app.py

In app.py we import page1 and page2. When you run app.py it loads the layout from page1.py if you go to the pathname /page1 and the layout from page2.py if you go to /page2.

from dash import Dash, dcc, html, Input, Output, callback
from pages import page1, page2


app = Dash(__name__, suppress_callback_exceptions=True)
server = app.server

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])


@callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == '/page1':
        return page1.layout
    elif pathname == '/page2':
        return page2.layout
    else:
        return '404'

if __name__ == '__main__':
    app.run_server(debug=True)

Flat Project Structure

Another option for a multi-page structure is a flat project layout with callbacks and layouts in separate files:

File structure:

- app.py
- callbacks.py
- layouts.py

app.py

from dash import Dash, dcc, html, Input, Output, callback

from layouts import layout1, layout2
import callbacks

app = Dash(__name__, suppress_callback_exceptions=True)
server = app.server

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])

@callback(Output('page-content', 'children'),
              Input('url', 'pathname'))
def display_page(pathname):
    if pathname == '/page1':
         return layout1
    elif pathname == '/page2':
         return layout2
    else:
        return '404'

if __name__ == '__main__':
    app.run_server(debug=True)


callbacks.py

from dash import Input, Output, callback

@callback(
    Output('page-1-display-value', 'children'),
    Input('page-1-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'

@callback(
    Output('page-2-display-value', 'children'),
    Input('page-2-dropdown', 'value'))
def display_value(value):
    return f'You have selected {value}'

layouts.py

from dash import dcc, html

layout1 = html.Div([
    html.H3('Page 1'),
    dcc.Dropdown(
        {f'Page 1 - {i}': f'{i}' for i in ['New York City', 'Montreal', 'Los Angeles']},
        id='page-1-dropdown'
    ),
    html.Div(id='page-1-display-value'),
    dcc.Link('Go to Page 2', href='/page2')
])

layout2 = html.Div([
    html.H3('Page 2'),
    dcc.Dropdown(
        {f'Page 2 - {i}': f'{i}' for i in ['London', 'Berlin', 'Paris']},
        id='page-2-dropdown'
    ),
    html.Div(id='page-2-display-value'),
    dcc.Link('Go to Page 1', href='/page1')
])

The multi-page app examples here use the decorator @callback, which is new with Dash 2.0. It simplifies creating multi-page apps. More changes to make it even easier to create multi-page apps are also on the way in future versions of Dash. Big thanks to the Dash Community for contributing to these features with Dash Labs.
Read more about other ways we’re working on simplifying multi-page apps.