Streamlit multi-page apps
Structure and navigation for apps with multiple pages.
Directory structure
streamlit_app.py # Main entry point
app_pages/
home.py
analytics.py
settings.py
Important: Name your pages directory app_pages/ (not pages/). Using pages/ conflicts with Streamlit's old auto-discovery API and can cause unexpected behavior.
Main module
python1# streamlit_app.py 2import streamlit as st 3 4# Initialize global state (shared across pages) 5if "api_client" not in st.session_state: 6 st.session_state.api_client = init_api_client() 7 8# Define navigation 9page = st.navigation([ 10 st.Page("app_pages/home.py", title="Home", icon=":material/home:"), 11 st.Page("app_pages/analytics.py", title="Analytics", icon=":material/bar_chart:"), 12 st.Page("app_pages/settings.py", title="Settings", icon=":material/settings:"), 13]) 14 15# App-level UI runs before page content 16# Useful for shared elements like titles 17st.title(f"{page.icon} {page.title}") 18 19page.run()
Note: When you handle titles in streamlit_app.py, individual pages should NOT use st.title again.
Navigation position
Few pages (3-7) → Top navigation:
python1page = st.navigation([...], position="top")
Creates a clean horizontal menu. Great for simple apps. Sections are supported too—they appear as dropdowns in the top nav.
Many pages or nested sections → Sidebar:
python1page = st.navigation({ 2 "Main": [ 3 st.Page("app_pages/home.py", title="Home"), 4 st.Page("app_pages/analytics.py", title="Analytics"), 5 ], 6 "Admin": [ 7 st.Page("app_pages/settings.py", title="Settings"), 8 st.Page("app_pages/users.py", title="Users"), 9 ], 10}, position="sidebar")
Mixed: Some pages ungrouped:
Use an empty string key "" for pages that shouldn't be in a section. These ungrouped pages always appear first, before any named groups. Put all ungrouped pages in a single "" key:
python1page = st.navigation({ 2 "": [ 3 st.Page("app_pages/home.py", title="Home"), 4 st.Page("app_pages/about.py", title="About"), 5 ], 6 "Analytics": [ 7 st.Page("app_pages/dashboard.py", title="Dashboard"), 8 st.Page("app_pages/reports.py", title="Reports"), 9 ], 10}, position="top")
Page modules
python1# app_pages/analytics.py 2import streamlit as st 3 4# Access global state 5api = st.session_state.api_client 6user = st.session_state.user 7 8# Page-specific content (title is handled in streamlit_app.py) 9data = api.fetch_analytics(user.id) 10st.line_chart(data)
Global state
Initialize state in the main module only if it's needed across multiple pages:
python1# streamlit_app.py 2st.session_state.api = init_client() 3st.session_state.user = get_user() 4st.session_state.settings = load_settings()
Tip: Use st.session_state.setdefault("key", default_value) to initialize values only if they don't exist.
Why main module (for global state):
- Runs before every page
- Ensures state is initialized
- Single source of truth
Page-specific state
Use prefixed keys for page-specific state:
python1# app_pages/analytics.py 2if "analytics_date_range" not in st.session_state: 3 st.session_state.analytics_date_range = default_range()
Share elements between pages
Put shared UI in the entrypoint (before page.run()) so it appears on all pages:
python1# streamlit_app.py 2import streamlit as st 3 4pages = [...] 5page = st.navigation(pages) 6 7# Shared title 8st.title(f"{page.icon} {page.title}") 9 10# Shared sidebar widgets 11with st.sidebar: 12 st.selectbox("Theme", ["Light", "Dark"]) 13 14page.run()
Programmatic navigation
Navigate to another page programmatically:
python1if st.button("Go to Settings"): 2 st.switch_page("app_pages/settings.py")
Create navigation links with st.page_link:
python1st.page_link("app_pages/home.py", label="Home", icon=":material/home:") 2st.page_link("https://example.com", label="External", icon=":material/open_in_new:")
Note: Prefer
st.navigationoverst.page_linkfor standard navigation. Do not usest.page_linkto recreate the nav bar you get withst.navigation. Only usest.page_linkwhen linking to pages from somewhere other than the sidebar, or when building a more complex navigation menu.
Conditional pages
Show different pages based on user role, authentication, or any other condition by building the pages list dynamically:
python1# streamlit_app.py 2import streamlit as st 3 4pages = [st.Page("app_pages/home.py", title="Home", icon=":material/home:")] 5 6if st.user.is_logged_in: 7 pages.append(st.Page("app_pages/dashboard.py", title="Dashboard", icon=":material/bar_chart:")) 8 9if st.session_state.get("is_admin"): 10 pages.append(st.Page("app_pages/admin.py", title="Admin", icon=":material/settings:")) 11 12page = st.navigation(pages) 13page.run()
Common conditions for showing/hiding pages:
st.user.is_logged_infor authenticated usersst.session_stateflags (roles, permissions, feature flags)- Environment variables or secrets
- Time-based access (e.g., beta features)
Imports from pages
When importing from page files in app_pages/, always import from the root directory perspective:
python1# app_pages/dashboard.py - GOOD 2from utils.data import load_sales_data 3 4# app_pages/dashboard.py - BAD (don't use relative imports) 5from ..utils.data import load_sales_data