Multi-Page Apps and URL Support

Dash renders web applications as a “single-page app”. This means that the application does not completely reload when the user navigates the application, making browsing very fast.

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.

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.