mrkeyoor.com_
Wed 05 Aug 05:03 UTC
PyPIDataupdated 05 Aug 2026

streamlit

Streamlit turns a plain Python script into an interactive web app: you write top-to-bottom Python with calls like st.slider and st.dataframe, run streamlit run app.py, and get a browser UI with live reload. The core mental model is unusual and non-negotiable: on every user interaction the entire script reruns from the top, and you manage persistence through st.session_state and the caching decorators. It is owned by Snowflake, ships a free Community Cloud for deployment, and has become the default way data scientists put a dashboard, internal tool, or LLM chat UI in front of people without writing JavaScript.

Verdict

Unbeatable speed from Python script to working data app, which is exactly what it is for. Treat it as the prototype and internal-tool layer; if an app succeeds and needs real scale, custom UI, or embedding, that success is your signal to rebuild on a web framework.

API stability4/5On 1.x since 2021 with the core st.* API essentially unchanged; deprecations (like the old caching primitives replaced by cache_data/cache_resource) come with long warning periods, though config options get shuffled between releases.
Docs5/5docs.streamlit.io has a full API reference with live examples, concept guides that actually explain the rerun model, a changelog, and an active community forum.
Maintenance4/5Pushed to daily with a steady release cadence (1.60.0) and Snowflake funding, but roughly 1200 open issues and PRs and external pull requests are currently paused, so the roadmap is whatever the internal team prioritizes.
Ecosystem4/5A real third-party components ecosystem (streamlit-extras, custom component gallery), Community Cloud, and huge LLM-tutorial mindshare, but far fewer building blocks than the JavaScript world it substitutes for.

Use it if

  • You want a data app or internal dashboard in an afternoon, in pure Python, with widgets, charts, and dataframe display built in
  • You are prototyping an LLM chat interface: st.chat_message, st.chat_input, and st.write_stream make a working chatbot UI in about 30 lines
  • Your users are a team, not the internet: tens of concurrent viewers on an internal tool is exactly the load profile it handles well
  • You need free hosting for demos: Community Cloud deploys straight from a GitHub repo
Skip it if

Setup reality

pip install streamlit && streamlit hello works in minutes, but note the install is heavy (pandas and pyarrow come along) and Python 3.10+ is required. The lasting cost is the execution model: until the rerun-everything loop clicks, you will fight duplicate-widget-key errors, state that resets when you expected it to stick, and expensive functions rerunning on every interaction until you wrap them in @st.cache_data or @st.cache_resource. Deployment means keeping a streamlit run process alive behind a websocket-friendly proxy (or handing the app to Community Cloud); it does not mount into gunicorn/WSGI like a normal Python web app.

Patterns

Smallest possible apphello-app

# streamlit_app.py
import streamlit as st

x = st.slider("Select a value")
st.write(x, "squared is", x * x)

# run with: streamlit run streamlit_app.py

The whole script reruns on every slider move; that is the core execution model, not a bug.

Cache expensive data loadscache-data

import pandas as pd
import streamlit as st

@st.cache_data(ttl=3600)
def load_data(url: str) -> pd.DataFrame:
    return pd.read_csv(url)

df = load_data("https://example.com/big.csv")
st.dataframe(df)

cache_data returns a fresh copy per call and is keyed on the function's arguments; without it this CSV downloads again on every widget interaction.

Cache global resources (models, DB connections)cache-resource

import streamlit as st

@st.cache_resource
def get_model():
    from sentence_transformers import SentenceTransformer
    return SentenceTransformer("all-MiniLM-L6-v2")

model = get_model()

cache_resource returns the same shared object to every session and rerun; use it for unpicklable things, and remember mutations are visible to all users.

Persist state across rerunssession-state

import streamlit as st

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

if st.button("Increment"):
    st.session_state.count += 1

st.write("Count:", st.session_state.count)

Plain variables reset on every rerun; session_state is per browser tab and dies with the session, it is not shared or persistent storage.

Batch inputs with a formform-batch-input

import streamlit as st

with st.form("signup"):
    name = st.text_input("Name")
    age = st.number_input("Age", min_value=0)
    submitted = st.form_submit_button("Submit")

if submitted:
    st.success(f"Saved {name}, {age}")

Widgets inside a form do not trigger reruns until the submit button; this is the fix when every keystroke reruns an expensive script.

Upload and process a filefile-upload

import pandas as pd
import streamlit as st

uploaded = st.file_uploader("Choose a CSV", type="csv")
if uploaded is not None:
    df = pd.read_csv(uploaded)
    st.dataframe(df.describe())

The value is None until a file is chosen, so guard for it; uploads live in memory with a 200 MB default limit (server.maxUploadSize).

LLM chat interface with streamingchat-ui

import streamlit as st

if "messages" not in st.session_state:
    st.session_state.messages = []

for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.markdown(m["content"])

if prompt := st.chat_input("Say something"):
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    with st.chat_message("assistant"):
        reply = st.write_stream(stream_from_your_llm(prompt))
    st.session_state.messages.append({"role": "assistant", "content": reply})

You must replay message history yourself on each rerun; st.write_stream accepts a generator and returns the full concatenated text.

Columns, sidebar, and tabslayout-columns

import streamlit as st

with st.sidebar:
    mode = st.radio("Mode", ["Overview", "Detail"])

col1, col2 = st.columns(2)
col1.metric("Revenue", "$12k", "+8%")
col2.metric("Users", "430", "-2%")

tab1, tab2 = st.tabs(["Chart", "Raw data"])
with tab1:
    st.line_chart(df)
with tab2:
    st.dataframe(df)

All tabs execute on every rerun even when not visible; heavy work inside a background tab still costs you.

Multi-page app with st.navigationmultipage-app

# app.py (entrypoint)
import streamlit as st

pages = st.navigation([
    st.Page("pages_src/home.py", title="Home", icon=":material/home:"),
    st.Page("pages_src/report.py", title="Report"),
])
pages.run()

# run with: streamlit run app.py

st.navigation/st.Page supersedes the old pages/ directory convention and gives programmatic control (auth-gated pages, dynamic menus).

Rerun only a fragment of the apppartial-rerun

import streamlit as st

@st.fragment(run_every="10s")
def live_prices():
    st.dataframe(fetch_prices())

st.title("Dashboard")  # not rerun by the fragment
live_prices()

Interactions with widgets inside a fragment rerun only the fragment; run_every gives you polling without rerunning the whole script.

Keep API keys out of the scriptsecrets-config

# .streamlit/secrets.toml  (never commit this)
# OPENAI_API_KEY = "sk-..."

import streamlit as st

key = st.secrets["OPENAI_API_KEY"]

st.secrets reads .streamlit/secrets.toml locally and the app settings UI on Community Cloud; add the file to .gitignore before your first commit, not after.

Alternatives

PackageRegistryPick it when
gradioPyPIYou are demoing an ML model and want instant share links and Hugging Face Spaces hosting
dashPyPIProduction analytics dashboards where callback-based control and scaling behavior matter more than authoring speed
panelPyPIYou live in Jupyter notebooks or need to work across many plotting libraries with finer reactive control