Streamlit Guide
Streamlit Multi-Page Apps
Turn a single-file script into a full app with navigation, pages, and shared state.
Last updated: October 2026 · Tested with Streamlit 1.40+
Quick answer: Create a pages/ directory next to your app.py. Add one .py file per page. Streamlit automatically adds them to the sidebar navigation. Share state with st.session_state. For full control, use st.navigation() and st.Page().
The Folder Structure
The simplest multi-page app has this layout:
my-app/
├── app.py # Home page
├── pages/
│ ├── 1_Dashboard.py
│ ├── 2_Settings.py
│ └── 3_Reports.py
├── requirements.txt
└── .streamlit/
└── secrets.toml
Key rules:
- The
pages/folder must be next toapp.py. - Number prefixes (
1_,2_) control the order in the sidebar. - Underscores in filenames become spaces in the sidebar.
- Emojis in filenames become page icons.
Your Home Page (app.py)
import streamlit as st
st.set_page_config(
page_title="My App",
page_icon="🏠",
layout="wide"
)
st.title("🏠 Home")
st.write("Welcome to my multi-page Streamlit app.")
st.page_link("pages/1_Dashboard.py", label="Go to Dashboard", icon="📊")
st.page_link("pages/2_Settings.py", label="Open Settings", icon="⚙️")
The st.page_link creates internal navigation buttons — great for landing pages.
Your First Sub-Page
Create pages/1_Dashboard.py:
import streamlit as st
import pandas as pd
import numpy as np
st.set_page_config(page_title="Dashboard", page_icon="📊")
st.title("📊 Dashboard")
st.write("This is the dashboard page.")
# Sample data
df = pd.DataFrame(
np.random.randn(20, 3),
columns=["Sales", "Users", "Revenue"]
)
st.line_chart(df)
st.dataframe(df)
That's it. Streamlit auto-detects the page and adds it to the sidebar.
Sharing State Across Pages
Use st.session_state to keep data between pages:
# pages/1_Dashboard.py
if "user" not in st.session_state:
st.session_state.user = {"name": "Alice"}
st.write(f"Welcome, {st.session_state.user['name']}")
# pages/2_Settings.py
st.write(f"Editing settings for {st.session_state.user['name']}")
if st.button("Rename to Bob"):
st.session_state.user["name"] = "Bob"
st.success("Updated!")
💡 Key insight: Session state is per-user, per-session. Each browser tab that opens your app has its own independent state. This is exactly what you want.
Programmatic Navigation with st.navigation
For full control over the sidebar — grouping, hiding pages based on login, custom order — use st.navigation:
import streamlit as st
st.set_page_config(page_title="My App")
# Define pages programmatically
home = st.Page("app_pages/home.py", title="Home", icon="🏠", default=True)
dash = st.Page("app_pages/dashboard.py", title="Dashboard", icon="📊")
admin = st.Page("app_pages/admin.py", title="Admin", icon="🔒")
# Group them
pages = {
"Main": [home, dash],
"Admin": [admin] if st.session_state.get("is_admin") else [],
}
# Run navigation
pg = st.navigation(pages)
pg.run()
The advantage: you control visibility based on authentication. Non-admin users never see the Admin page.
Don't mix approaches: Use either the pages/ directory or st.navigation — not both. Mixing them leads to duplicated pages in the sidebar.
Real-World Example: AI Chatbot with Pages
Here's how to structure a chatbot with a chat page, history page, and settings:
ai-app/
├── app.py # Home / landing
├── pages/
│ ├── 1_Chat.py # Chat interface
│ ├── 2_History.py # Saved conversations
│ └── 3_Settings.py # API keys, model picker
└── utils/
├── openai_client.py
└── storage.py
Each page imports from utils/, keeping the code DRY. The chat state lives in st.session_state.
👉 Read our Streamlit AI Chatbot Tutorial.
🎨 Sidebar Navigation Customization
| What you want | How to do it |
|---|---|
| Icons on pages | Add emoji to filename: 1_📊_Dashboard.py |
| Custom page title | st.set_page_config(page_title="...") |
| Reorder pages | Prefix filenames with numbers |
| Hide a page | Prefix with underscore: _hidden.py |
| Group pages | Use st.navigation() with a dict |
| Auth-based visibility | Filter pages conditionally with st.navigation() |
🛡️ Best Practices
- Call
st.set_page_config()once per page — it must be the first Streamlit command. - Keep page files focused — one clear purpose each.
- Put shared logic in a
utils/orlib/folder and import it. - Use
st.session_statefor state that spans pages. - Number filenames to control order, but keep names descriptive.
- Cache expensive operations with
@st.cache_data— the cache is shared across pages. - For pages with different widths, set
layout="wide"only on pages that need it.
Common mistake: Putting st.set_page_config() after other st commands. It must be the first Streamlit call or it throws an error.
❓ Frequently Asked Questions
How do I create a multi-page app in Streamlit?
Create a pages/ directory next to your main app.py. Add one .py file per page. Streamlit automatically adds them to the sidebar navigation. Each page file is run like a normal Streamlit script.
Does Streamlit support multiple pages?
Yes. Streamlit has built-in multi-page support via the pages/ directory. Since Streamlit 1.36, you can also use st.navigation and st.Page for programmatic control.
How do I share state between Streamlit pages?
Use st.session_state. Data stored there persists across page navigation within the same session. You cannot share state between different users — each user has their own session.
How do I customize the Streamlit sidebar navigation?
Use st.navigation() to define pages programmatically. This lets you control page titles, icons, grouping, and order. It also lets you hide pages based on authentication.
Can I have a landing page in Streamlit?
Yes. Your main app.py acts as the default landing page. Users land on it when they first visit your app. Use st.page_link to link to other pages from the landing page.