Session state and callbacks

Streamlit · Practical · 14 min

Counters, multi-step flows, chat histories and any app that remembers anything depend on session state.

st.session_state is a dictionary-like object that persists across reruns for one user session (one browser tab). Use attribute or key syntax:

if "count" not in st.session_state:
    st.session_state.count = 0
st.session_state.count += 1

Always initialize keys before use, because the first run starts empty.

Widgets and state are linked: a widget with key="city" stores its value in st.session_state.city. You can read it anywhere, and you can set it before the widget is created to change the widget programmatically. Setting it after the widget was created in the same run raises an error.

Callbacks run before the rerun's script, which makes state updates take effect immediately:

st.button("+1", on_click=increment, args=(1,))
st.text_input("Name", key="name", on_change=normalize_name)

Without a callback, if st.button(...): st.session_state.count += 1 updates the state after any earlier lines have already displayed the old value, a common source of "off by one click" bugs.

Session state is per tab: a page refresh or a new tab starts a new, empty session. For data that must survive restarts or be shared between users, use a database or file, not session state.

Widget values are removed from session state when the widget is not rendered in a run (for example on another page), so copy values you need to keep into your own keys.

Example

import streamlit as st

if "count" not in st.session_state:          # initialize once per session
    st.session_state.count = 0
    st.session_state.history = []

def add(step):                               # callback: runs before the script
    st.session_state.count += step
    st.session_state.history.append(step)

def shout():
    st.session_state.name = st.session_state.name.upper()

st.button("+1", on_click=add, args=(1,))
st.button("+10", on_click=add, args=(10,))
st.write("Count:", st.session_state.count)   # already updated: no lag
st.write("Steps:", st.session_state.history)

st.text_input("Name", key="name", on_change=shout)   # value lives in state
st.write("Stored:", st.session_state.get("name", ""))

Remember

  • st.session_state persists values across reruns within one browser tab
  • Widget keys map widget values into session state
  • Callbacks (on_click, on_change) update state before the script reruns

Watch out for

  • Updating state inside if st.button(...) after the value was already displayed, so the page lags one click behind.

Check yourself

  1. A user refreshes the browser page. What happens to st.session_state?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.streamlit.io/develop/concepts/architecture/session-state