Multipage Apps
Streamlit provides two built-in mechanisms for creating multipage apps.
- The simplest method is to use a
pages/directory. - However, the preferred and more customizable method is to use
st.navigation.
pages/ directory
For a quick and easy way to add multiple pages to your Streamlit app, simply create a pages/ directory alongside your main entry point file (the file you pass to streamlit run). Each Python file in the pages/ directory will automatically become a new page in your app. Streamlit will generate the page labels and URLs based on the file names and display a navigation menu at the top of the app's sidebar.
your_working_directory/
├── pages/
│ ├── a_page.py
│ └── another_page.py
└── your_homepage.pyIf you want more control over the navigation, you can disable the default navigation by setting client.showSidebarNavigation = false in your configuration. Then, you can manually build a custom menu using st.page_link, which allows you to modify page labels and icons, though the URLs will remain unchanged.
st.Page and st.navigation
If you want maximum flexibility in defining your multipage app, use st.Page and st.navigation. With st.Page you can declare any Python file or Callable (any object that can be invoked like a function) as a page in your app. You can also define common elements in your entry point file, which acts as a shared "frame" for all pages.
You must include st.navigation in your entry point file to configure your app's navigation menu. This also allows the entry point file to act as the router, managing navigation between pages.
NOTE
You can call st.navigation only once per app run.
If you call st.navigation in your app (in any session), Streamlit will disregard pages/ directory, giving priority to st.navigation.
your-repository/
├── page_1.py
├── page_2.py
└── streamlit_app.py# Entry point file (streamlit_app.py)
import streamlit as st
# You can define shared content here that will be present on every page
st.title("Welcome to My Multipage Streamlit App!")
st.write("This is a shared element that will appear across all pages.")
pg = st.navigation([st.Page("page_1.py"), st.Page("page_2.py")])
# st.navigation returns the selected page and its .run() method should be invoked
pg.run()# page_1.py
import streamlit as st
st.write("This is the page_1 content!")Define pages using st.Page
st.Page allows you to define a page in your app. The main argument specifies the page source, which can be a Python file or a function.
Additionally, st.Page lets you configure page title, icon and URL pathname. However, if you want to set a consistent favicon and page title across all pages, use st.set_page_config after st.navigation, either in the entrypoint file or in the page source. This way, each page will have its own label and icon in the navigation menu, While the browser tab will display a consistent title and favicon for all pages.
import streamlit as st
create_page = st.Page("create.py", title="Create entry", icon=":material/add_circle:")
delete_page = st.Page("delete.py", title="Delete entry", icon=":material/delete:")
pg = st.navigation([create_page, delete_page])
st.set_page_config(page_title="Data manager", page_icon=":material/edit:")
pg.run()st.Page with a Callable
Here's a simple example that demonstrates how to use st.Page with a callable (a function) to define a page in a Streamlit app:
import streamlit as st
# Define a simple page as a callable (function)
def about():
st.title("About")
st.write("This is the about page of the app.")
# Configure your pages using st.Page
about_page = st.Page(about, title="About Us", icon="ℹ️")
# Register the pages in st.navigation
page = st.navigation([about_page])
page.run()Section Headers
You can customize the navigation menu to organize the pages using st.navigation. You can also sort, group or remove any pages you don't want the user to access.
For example, you can create two menu states: one for logged-out users, showing only the login page, and another for logged-in users, displaying more options. If an unlogged user tries to access a restricted page, they will be redirected to the login page. After login, users will see a full menu and be directed to the dashboard as the default page.
your-repository/
├── reports
│ ├── alerts.py
│ ├── bugs.py
│ └── dashboard.py
├── tools
│ ├── history.py
│ └── search.py
└── streamlit_app.py# streamlit_app.py
import streamlit as st
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
def login():
if st.button("Log in"):
st.session_state.logged_in = True
st.rerun()
def logout():
if st.button("Log out"):
st.session_state.logged_in = False
st.rerun()
login_page = st.Page(login, title="Log in", icon=":material/login:")
logout_page = st.Page(logout, title="Log out", icon=":material/logout:")
dashboard = st.Page(
"reports/dashboard.py", title="Dashboard", icon=":material/dashboard:", default=True
)
bugs = st.Page("reports/bugs.py", title="Bug reports", icon=":material/bug_report:")
alerts = st.Page(
"reports/alerts.py", title="System alerts", icon=":material/notification_important:"
)
search = st.Page("tools/search.py", title="Search", icon=":material/search:")
history = st.Page("tools/history.py", title="History", icon=":material/history:")
if st.session_state.logged_in:
pg = st.navigation(
{
"Account": [logout_page],
"Reports": [dashboard, bugs, alerts],
"Tools": [search, history],
}
)
else:
pg = st.navigation([login_page])
pg.run()Statefulness of widgets
As explained in a previous page, you can maintain widget state across pages by using a duplicate key or by interrupting widget clean-up process. In addition to these, when you define the multipage page using st.navigation, you can also have a stateful widget across all pages, by placing it in the entry point file.
NOTE
This method does not work if you define your app with the pages/ directory.
import streamlit as st
pg = st.navigation([st.Page("page_1.py"), st.Page("page_2.py")])
st.sidebar.selectbox("Group", ["A","B","C"], key="group")
st.sidebar.slider("Size", 1, 5, key="size")
pg.run()The selectbox and slider widgets in the sidebar are rendered and stateful on all pages.
Building a Custom Navigation Menu
For more control over your navigation menu, you can hide the default one and build your own. To do this, include position="hidden" in your st.navigation command. If you need a page to be accessible without showing it in the navigation menu, this method is required. Keep in mind, a page must be included in st.navigation to be accessible by any means, including navigation by URL, st.switch_page, or st.page_link. Without this, users won't be able to reach the page.
Page Terminology
In Streamlit, each page is identified by four key elements:
- Page source: The Python file or callable function containing the page's source code.
- Page label: The name displayed in the app's navigation menu (See 1).
- Page title: The content of the HTML
<title>element, which appears on the browser tab (See 2). - Page URL pathname: The relative URL path of the page from the app's root URL (See 3).
Additionally, a page can have two types of icons:
- Favicon: The icon displayed next to the page title in the browser tab.
- Page icon: The icon shown next to the page label in the app's navigation menu.
By default, the page icon and favicon are the same, but you can set them to be different if needed.

Page Structure and Navigation
This section (and its subsections) applies to both methods of declaring Multipage apps.
IMPORTANT
If you explicitly declare the page title or URL pathname with st.Page, they will take precedence over the following rules.
Page Filenames
Page filenames are composed of four different parts as follows:
number: A non-negative integer.separator: Any combination of underscore (_), dash (-), and space ().identifier: Everything up to, but not including,.py..pyextension.
For callables, the function name is the identifier, including any leading or trailing underscores.
TIP
You can also use Emojis in the page names. They will be part of the identifier above.
Page Labels and Titles
Streamlit generates page labels and titles in the following way:
- If the page has an
identifier, Streamlit uses theidentifier. It converts any underscores (_) into spaces and removes leading or trailing underscores. Consecutive underscores are collapsed into a single space. - If the page has a
numberbut noidentifier, Streamlit uses thenumberunmodified, keeping any leading zeros intact. - If the page only has a
separator(with nonumberoridentifier), Streamlit will not display the page in the sidebar navigation.
Pages Order
Streamlit pages are sorted using following rules:
- The entry point file is always displayed first.
- Then, files that have a
numberappear before files without anumber. - Files are sorted based on the
number(if any), followed by thelabel(if any). - When files are sorted, Streamlit treats the
numberas an actual number rather than a string. So03is the same as3.
Page URLs
Streamlit URLs are determined by the following rules (in order):
- The homepage, typically the entry point file, is linked to the root URL of the app.
- If the page filename contains an
identifier, Streamlit uses it after replacing consecutive spaces () or underscores (_) with a single underscore. - If the page is navigated from a callable, Streamlit uses the
identifierof the callable unmodified. - If the page filename contains a
numberbut noidentifier, Streamlit uses thenumber, preserving any leading zeros.
Navigating between pages
Users primarily navigate between pages using the navigation widget, which appears in the sidebar by default (for both methods). Optionally, You can hide the default navigation menu and create your own using st.page_link.
See this tutorial to create a custom navigation menu.
If you need to programmatically switch pages, use st.switch_page.
Users can also navigate directly via URLs. If multiple pages share the same URL pathname, Streamlit selects the first one in the navigation menu order.
WARNING
Navigating by URL creates a new browser session, which resets st.session_state. To maintain state across pages, use Streamlit's built-in navigation methods like st.navigation, st.switch_page, st.page_link, or the default menu.
If a user tries to access a non-existent page URL, they will see a "Page not found" modal.
