lib/streamlit/.agents/skills/developing-with-streamlit/references/session-state.md
Streamlit reruns scripts top-to-bottom on every interaction. Without session state, variables reset each time. Use st.session_state to persist values across reruns.
Session state is a dictionary-like object supporting attribute and bracket notation:
# Initialize with setdefault (preferred)
st.session_state.setdefault("count", 0)
# Alternative: check before setting
if "count" not in st.session_state:
st.session_state.count = 0
# Read
current = st.session_state.count
# Update
st.session_state.count += 1
st.session_state["count"] = 5 # Bracket notation also works
# Delete
del st.session_state.count
Accessing uninitialized keys raises KeyError. Use st.session_state.get("key", default) for safe access.
Every widget with a key parameter automatically syncs to session state:
name = st.text_input("Name", key="user_name")
# st.session_state.user_name contains the same value as `name`
persist_state)By default, a keyed widget's value is lost when the widget stops being rendered (for example, when it's conditionally hidden or the user switches pages). Set the keyword-only persist_state parameter to keep the value:
None (default): the value is dropped when the widget isn't rendered."page": the value is preserved while the user stays on the page where the widget is defined (e.g., while it's conditionally hidden); it's discarded on a page switch."session": the value is preserved for the whole session, including across page switches, so it returns when the user navigates back.st.text_input("Name", key="name", persist_state="session")
persist_state requires a key and is available on every widget that supports bind="query-params". When both are set, bind takes precedence, so the value lives in the URL and persists across page switches regardless of the persist_state scope.
Callbacks execute before the script reruns, allowing immediate state changes. Use on_change or on_click with optional args and kwargs:
def increment(amount):
st.session_state.count += amount
st.button("Add 5", on_click=increment, args=(5,))
Access a widget's value in its own callback via st.session_state.key, not the return variable.
Initialize all state at the top of your app for clarity:
st.session_state.setdefault("user", None)
st.session_state.setdefault("page", "home")
st.session_state.setdefault("filters", {})
By default, widgets are NOT stateful across pages—their values reset when navigating between pages. To keep a widget's value across page switches, set persist_state="session" (see Persisting widget values above).
Use session state variables (not widget keys) to share data:
# Page 1: Store value
st.session_state.selected_user = st.selectbox("User", users)
# Page 2: Read stored value
if "selected_user" in st.session_state:
st.write(f"Selected: {st.session_state.selected_user}")
Put common widgets in the entrypoint file (before nav.run()):
# app.py (entrypoint)
with st.sidebar:
st.session_state.theme = st.selectbox("Theme", ["Light", "Dark"])
nav = st.navigation(pages)
nav.run()
# BAD: In imported modules, this is shared across ALL users
# utils.py
cache = {} # Persists across reruns AND users!
# GOOD: Use session state for per-user data
st.session_state.setdefault("cache", {})
Cannot assign to a widget's state after the widget has rendered:
st.slider("Value", key="my_slider")
st.session_state.my_slider = 50 # Raises StreamlitAPIException!
value parameter and session stateDon't set both—it causes warnings:
# BAD: Conflicting sources
st.session_state.setdefault("name", "Alice")
st.text_input("Name", value="Bob", key="name") # Warning!
# GOOD: Use one or the other
st.session_state.setdefault("name", "Alice")
st.text_input("Name", key="name")