Keep Streamlit Widget Values Alive Across Reruns
Streamlit reruns your script on every interaction. Learn how st.session_state preserves widget values and counters without resetting user input.
20 May 2026, 21:52 UTC

Your text input vanishes the moment you click a button
Streamlit reruns the entire Python script from top to bottom every time a user interacts with a widget. If you store a value in an ordinary Python variable, that variable is recreated from scratch on the next run. A user can type a name into a text box, click a button, and find the box empty again because the script forgot what happened before.
The useful takeaway is to bind widgets and calculated values to st.session_state, Streamlit’s per-session dictionary that survives automatic reruns.
How the rerun model resets ordinary variables
When a user changes a widget, Streamlit executes your script again from line one. Local variables such as counter = 0 are reset to their initial values every time. st.session_state behaves like a persistent dictionary for one browser session, so values written there remain available after the next rerun.
| Storage | Survives rerun? | Shared across users? |
|---|---|---|
| Local variable | No | N/A |
| st.session_state | Yes, per session | No |
Bind widgets to state with the key argument
Pass a key to a widget so Streamlit reads and writes that widget’s value directly into session state:
import streamlit as st
st.text_input("Your name", key="user_name")
st.write("Hello, " + st.session_state.get("user_name", ""))
On the first run, st.session_state["user_name"] may not exist yet, so use .get() or initialize it first.
Initialize state once so reruns do not wipe it
Use a membership guard before assigning a default. This prevents the script from resetting user input on every rerun:
if "counter" not in st.session_state:
st.session_state["counter"] = 0
if st.button("Increment"):
st.session_state["counter"] += 1
st.write("Count:", st.session_state["counter"])
The guard runs only when the key is absent. After the button click, the script reruns, the key already exists, and the incremented value survives.
Worked example: a counter and a name field
Save the following as state_app.py and run it from your terminal:
streamlit run state_app.py
Where to run: a terminal in the project directory. Permissions: none beyond normal file execution. Expected checks: type a name, click Increment several times, and confirm both the name and count remain after each rerun. Risk: do not store large DataFrames or machine-learning models here; each session holds its own copy in memory.
import streamlit as st
if "counter" not in st.session_state:
st.session_state["counter"] = 0
if "user_name" not in st.session_state:
st.session_state["user_name"] = ""
def increment():
st.session_state["counter"] += 1
st.text_input("Your name", key="user_name")
st.button("Increment", on_click=increment)
st.write("Name:", st.session_state["user_name"])
st.write("Count:", st.session_state["counter"])
Open the same app in two browser tabs and change the name in one. The other tab should keep its own value because session state is isolated per browser session.
Limitation: session state is not a shared cache
st.session_state is private to one browser tab or session. It is not shared between users, and it disappears when the session ends. Storing large objects increases memory per session and can reduce how many concurrent users the app can support. Use a database or server-side cache if multiple users need to see the same data.
Check that it works
- Run
python -c "import streamlit; print(streamlit.__version__)"to confirm your installed Streamlit release. - Run
streamlit run state_app.py. - Interact with the widget, then trigger a rerun by clicking a button.
- Open a second tab and verify values are independent.
If values reset, check that every widget uses a stable key and that defaults are set behind an if ... not in st.session_state guard. Widget key semantics can change between releases, so review the changelog for your installed version.
Closing
Streamlit’s rerun model is simple until it erases your user’s input. Treat st.session_state as the place for values that must outlive a single script execution, initialize them with a guard, and keep large objects out of it. That pattern keeps forms, counters, and multi-step flows predictable without adding an external database.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.