How to Implement Session State Management in Streamlit Apps: A Complete Guide

Use st.session_state as a persistent dictionary to store variables across script reruns, initializing keys at the top of your app to prevent data loss on every user interaction.

Streamlit's execution model reruns the entire script on every button click or input change, which resets standard Python variables. To build interactive LLM applications that remember chat history, API keys, or loaded documents, you must implement robust session state management in Streamlit apps. The Shubhamsaboo/awesome-llm-apps repository demonstrates production-grade patterns for persisting complex objects and avoiding redundant API calls.

Why Streamlit Session State Management Is Essential

Streamlit apps operate on a stateless request-response cycle. Without st.session_state, every widget interaction wipes your data, forcing users to re-enter API keys or causing duplicate downloads of large transcripts. The session state dictionary survives reruns, acting as a global mutable store that lives outside the script's local scope.

Initializing Session State Keys to Prevent KeyError

Always declare required keys at the top of your script using existence checks. This guarantees the key exists before you read or write it later, preventing KeyError exceptions on the first run.

import streamlit as st

# Declare required keys once

keys = {
    "app": None,
    "current_video_url": None,
    "transcript_loaded": False,
    "transcript_text": None,
    "word_count": 0,
    "chat_history": [],
}
for k, default in keys.items():
    if k not in st.session_state:
        st.session_state[k] = default

Conditional Resource Loading with Session State

Use session state flags to load heavy resources only when necessary. In advanced_llm_apps/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py, the app fetches YouTube transcripts only when the URL changes or when the transcript_loaded flag is false.

video_url = st.text_input("YouTube URL")
if video_url and (video_url != st.session_state.current_video_url
                  or not st.session_state.transcript_loaded):
    with st.spinner("Fetching transcript…"):
        title, transcript = fetch_video_data(video_url)
        if "No transcript" not in transcript:
            # Reset state for new video

            if st.session_state.transcript_loaded:
                st.session_state.app = None          # new vector store

                st.session_state.chat_history = []   # discard old chat

            # Store new data

            st.session_state.current_video_url = video_url
            st.session_state.transcript_text = transcript
            st.session_state.word_count = len(transcript.split())
            st.session_state.transcript_loaded = True

This pattern eliminates redundant API calls and ensures deterministic updates.

Storing Complex Objects in Streamlit Session State

st.session_state can store any picklable object, including full library instances. The awesome-llm-apps repository stores an Embedchain App instance after creation to avoid rebuilding the vector database on every rerun.

st.session_state.app = embedchain_bot(db_path, openai_access_token)

This approach is essential for maintaining expensive objects like database connections, ML models, or API clients across user interactions.

Building Persistent Chat History with Session State

Append each user question and AI answer to a list stored in session state. This preserves the conversation even after the user sends a new prompt.

prompt = st.text_input("Ask a question")
if prompt and st.session_state.transcript_loaded:
    answer = st.session_state.app.chat(prompt)
    st.session_state.chat_history.append((prompt, answer))
    st.write("**Answer:**", answer)

# Render history

if st.session_state.chat_history:
    with st.expander("Chat History"):
        for i, (q, a) in enumerate(st.session_state.chat_history, 1):
            st.markdown(f"**Q{i}:** {q}")
            st.markdown(f"**A{i}:** {a}")
            st.divider()

Resetting and Clearing Session State

Provide users with a mechanism to clear data and start fresh. Explicitly wipe relevant entries and call st.rerun() to refresh the UI immediately.

if st.button("🗑️ Clear Video"):
    for k in ["current_video_url", "transcript_loaded",
              "transcript_text", "word_count", "chat_history", "app"]:
        st.session_state[k] = keys[k]   # restore defaults

    st.rerun()                          # force UI refresh

This pattern ensures clean state transitions when switching between tasks or users.

Real-World Examples from awesome-llm-apps

The Shubhamsaboo/awesome-llm-apps repository provides several reference implementations demonstrating these patterns:

File Role Implementation Details
advanced_llm_apps/chat_with_X_tutorials/chat_with_youtube_videos/chat_youtube.py Full session-state workflow for transcript loading, vector store handling, chat history, and reset logic. View Source
advanced_llm_apps/chat_with_X_tutorials/chat_with_youtube_videos/test_session_state.py Unit-test that mimics session-state flow without a Streamlit UI, useful for verifying logic. View Source
voice_ai_agents/voice_rag_openaisdk/rag_voice.py Session-state for storing API keys, selected voice, processed documents, and multi-step pipeline state. View Source
starter_ai_agents/opeani_research_agent/research_agent.py Session-state managing research workflow state including conversation ID and collected facts. View Source

Summary

  • Initialize early: Check if 'key' not in st.session_state at the top of your script to prevent KeyError exceptions.
  • Load conditionally: Compare current inputs against stored session values to avoid redundant API calls and expensive recomputation.
  • Store rich objects: Use st.session_state to persist picklable instances like database connections, ML models, and API clients.
  • Persist conversations: Append user queries and responses to a list in session state to maintain chat history across reruns.
  • Reset cleanly: Provide explicit clear buttons that restore default values and call st.rerun() to refresh the UI.

Frequently Asked Questions

How do I prevent Streamlit from resetting my variables on every interaction?

Store variables in st.session_state, which acts as a persistent dictionary that survives script reruns. Initialize keys at the top of your script using if 'key' not in st.session_state to ensure they exist before use.

Can I store complex objects like machine learning models in session state?

Yes, st.session_state can store any picklable Python object, including ML models, database connections, and third-party library instances like Embedchain App objects. This avoids expensive reinitialization on every user interaction.

What is the best way to clear or reset session state in Streamlit?

Iterate over the keys you want to reset and restore them to their default values, then call st.rerun() to immediately refresh the UI. This pattern is useful when switching between videos, resetting chats, or clearing user data.

How do I avoid making duplicate API calls when the user interacts with my app?

Store the last used input value (like a URL) in st.session_state and compare it against the current input before executing the API call. Only fetch new data when the input changes or when a loading flag indicates the resource is not yet cached.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →