What Is Streamlit Python and Why 90% of Fortune 50 Companies Use It
Streamlit Python is an open-source framework that turns plain Python scripts into interactive web applications without requiring any front-end development experience. Instead of juggling HTML, CSS, and JavaScript, you write pure Python and Streamlit handles the rest – rendering widgets, managing state, and serving your app through a local development server or a production deployment pipeline. Since Snowflake acquired the project in 2022, the framework has matured rapidly, and Streamlit 1.55.0, released on March 3, 2026, represents the most capable version yet.
Last updated: April 10, 2026
The adoption numbers speak for themselves. Streamlit is trusted by more than 90% of Fortune 50 companies for internal data tools, rapid prototyping, and customer-facing dashboards. The GitHub repository has surpassed 38,000 stars, and the ecosystem now includes thousands of community-built components. If you have ever wished you could share a Jupyter notebook as a polished web app, this streamlit tutorial will show you exactly how to do it – in 12 concrete steps.
Why has streamlit python become the default choice for data scientists and ML engineers? Three reasons stand out. First, the API is radically simple: a single st.write() call can render text, DataFrames, charts, or even machine learning model outputs. Second, every widget automatically triggers a rerun of your script from top to bottom, which eliminates the callback spaghetti that plagues other frameworks. Third, deployment is trivial – you can push a public app to Streamlit Community Cloud in under two minutes or package it in a Docker container for enterprise infrastructure.
Version 1.55.0 introduced several features that enterprise teams have been requesting for years. Dynamic containers such as st.tabs, st.popover, and st.expander now accept an on_change parameter, letting you respond to user interactions without a full page rerun. Widget binding via the bind parameter enables query-parameter syncing, so users can bookmark a specific dashboard state. The experimental st.App ASGI-compatible entry point lets you mount a Streamlit application inside a FastAPI or Starlette server – a massive win for teams that need to serve both an API and a dashboard from the same process. Python 3.14 support, added in late 2025, ensures compatibility with the latest language features including type parameter syntax and improved pattern matching.
Throughout this streamlit tutorial, you will build a fully functional data dashboard from scratch. The project loads a real dataset, applies transformations with Pandas, renders interactive Plotly charts, adds authentication via session state, connects to a PostgreSQL database, and deploys to both Streamlit Community Cloud and Docker. By the end, you will have a production-grade streamlit dashboard that you can adapt for any analytical use case – from sales reporting to ML model monitoring. If you are also interested in building back-end APIs to complement your dashboard, check out our Flask tutorial and FastAPI guide.
Prerequisites and Environment Setup
Before diving into the streamlit tutorial, make sure your development machine meets the following requirements. Streamlit 1.55.0 supports Python 3.9 through 3.14, so any recent Python installation will work. We recommend Python 3.12 or later for the best balance of performance and library compatibility. You will also need pip (bundled with Python) and a code editor – VS Code with the Python extension is the most popular choice among streamlit python developers.
| Requirement | Minimum Version | Recommended Version | Purpose | Notes |
|---|---|---|---|---|
| Python | 3.9 | 3.12+ | Runtime | 3.14 supported since late 2025 |
| pip | 22.0 | 24.0+ | Package management | Ships with Python |
| Streamlit | 1.50.0 | 1.55.0 | Dashboard framework | Released March 3, 2026 |
| Pandas | 1.5.0 | 2.2+ | Data manipulation | Required by Streamlit internally |
| Plotly | 5.18.0 | 6.0+ | Interactive charts | Optional but recommended |
| PostgreSQL | 13 | 16+ | Database (Step 10) | Optional – SQLite works too |
| Docker | 24.0 | 27.0+ | Production deployment | Optional – for Step 12 only |
If you do not already have Python installed, download it from the official site and make sure to check the “Add Python to PATH” option during installation. On macOS you can use brew install [email protected], and on Ubuntu sudo apt install python3.12 python3.12-venv. We will use Python’s built-in venv module to create an isolated environment for our project, which prevents dependency conflicts with other Python projects on your machine.
You should also have basic familiarity with Python syntax – variables, functions, loops, and dictionaries. If you are coming from a data science background and are comfortable with Jupyter notebooks, you already know more than enough. For developers who want to brush up on Python fundamentals or explore automation capabilities, our Python automation tutorial covers the essentials. Git is not strictly required for this tutorial, but you will need it if you plan to deploy to Streamlit Community Cloud in Step 11. Install it from git-scm.com if it is not already on your system.
One more note before we start: Streamlit runs a local web server on port 8501 by default. If that port is in use on your machine, you can change it with the --server.port flag. Some corporate firewalls block non-standard ports, so check with your IT team if you plan to share your development server across a local network. With all prerequisites in place, let us install Streamlit and create our project.
Step 1 – Install Streamlit 1.55 and Create Your Project Structure
Every solid project starts with a clean directory structure and an isolated environment. Open your terminal and run the following commands to create the project folder, set up a virtual environment, and install the required packages.
# Create project directory and navigate into it
mkdir streamlit-dashboard && cd streamlit-dashboard
# Create a virtual environment
python3 -m venv .venv
# Activate the environment
# macOS / Linux:
source .venv/bin/activate
# Windows:
# .venvScriptsactivate
# Upgrade pip and install core dependencies
pip install --upgrade pip
pip install streamlit==1.55.0 pandas plotly psycopg2-binary openpyxl
# Verify the installation
streamlit version
# Output: Streamlit, version 1.55.0
The pip install command pulls Streamlit along with its internal dependencies (Tornado, Protobuf, Altair, and others) from PyPI. We also install pandas for data wrangling, plotly for interactive charts, psycopg2-binary for PostgreSQL connectivity, and openpyxl so Pandas can read Excel files. The total download size is approximately 120 MB.
Now create the directory structure that will support a multi-page streamlit dashboard. Streamlit automatically detects a pages/ folder and renders each Python file inside it as a separate page in the sidebar navigation.
# Project structure
streamlit-dashboard/
├── .streamlit/
│ └── config.toml # Theme and server configuration
├── pages/
│ ├── 1_Data_Explorer.py # Page 1
│ ├── 2_Visualizations.py # Page 2
│ └── 3_Database.py # Page 3
├── data/
│ └── sales_data.csv # Sample dataset
├── utils/
│ └── helpers.py # Shared utility functions
├── app.py # Main entry point
├── requirements.txt # Dependency list
└── Dockerfile # Docker deployment (Step 12)
Create a requirements.txt file so that anyone cloning the project can replicate your environment exactly. This file is also required for Streamlit Community Cloud deployment, which we cover in Step 11.
# requirements.txt
streamlit==1.55.0
pandas==2.2.3
plotly==6.0.1
psycopg2-binary==2.9.10
openpyxl==3.1.5
With the skeleton in place, freeze your dependency versions into the requirements file by running pip freeze > requirements.txt if you want the exact output of your environment. However, pinning only the top-level packages (as shown above) is generally cleaner and gives pip more flexibility to resolve sub-dependencies. You can always regenerate a full lock file later using pip-tools or poetry for production deployments. If you are interested in containerizing Python applications generally, our Docker tutorial walks through the full workflow.
Step 2 – Build Your First Streamlit App in 5 Lines of Code
The magic of streamlit python is how little boilerplate you need to get a working web application. Create a file called app.py in the project root and add the following code.
# app.py — Your first Streamlit application
import streamlit as st
import pandas as pd
st.set_page_config(page_title="Sales Dashboard", page_icon="📊", layout="wide")
st.title("Sales Dashboard")
st.markdown("Welcome to the **Sales Dashboard**. Use the sidebar to navigate between pages.")
# Quick health check — display a sample DataFrame
sample_data = pd.DataFrame({
"Region": ["North", "South", "East", "West", "Central"],
"Revenue": [120000, 98000, 145000, 87000, 110000],
"Units Sold": [1500, 1200, 1800, 1050, 1350],
"Growth (%)": [12.5, 8.3, 15.1, 6.7, 10.2],
})
st.subheader("Regional Performance Overview")
st.dataframe(sample_data, use_container_width=True)
# Display key metrics
col1, col2, col3 = st.columns(3)
col1.metric("Total Revenue", f"${sample_data['Revenue'].sum():,.0f}", "+10.6%")
col2.metric("Total Units", f"{sample_data['Units Sold'].sum():,}", "+8.4%")
col3.metric("Avg Growth", f"{sample_data['Growth (%)'].mean():.1f}%", "+2.1pp")
Run the application with a single terminal command:
streamlit run app.py
Your default browser will open to http://localhost:8501 and display the dashboard. The st.set_page_config call must be the first Streamlit command in your script – it configures the browser tab title, the favicon, and the layout mode. Setting layout="wide" uses the full browser width instead of the default centered column, which is essential for data-heavy dashboards.
Notice how Streamlit renders a Pandas DataFrame as an interactive, sortable table with zero configuration. The st.metric widget displays a number alongside a delta indicator – perfect for KPI cards. The st.columns function divides the page into horizontal sections, each of which behaves like an independent container. This layout primitive is the foundation of every streamlit dashboard you will build.
Every time you save app.py, Streamlit detects the file change and offers a “Rerun” button in the top-right corner of the browser. You can also enable “Always rerun” to get instant feedback as you code. This hot-reload workflow is one of the reasons data scientists prefer Streamlit over heavier frameworks – the feedback loop is measured in milliseconds, not minutes. If you have worked with Flask or Django, you will appreciate how much ceremony Streamlit removes from the development cycle.
Step 3 – Add Interactive Widgets and User Input Controls
A static table is useful, but a dashboard becomes powerful once users can filter, slice, and interact with the data themselves. Streamlit ships with more than 30 built-in widgets covering text inputs, sliders, dropdowns, date pickers, file uploaders, and more. Each widget returns a Python value that you use directly in your script – no callbacks, no event handlers, no state management boilerplate.
Add the following code below the existing content in app.py to introduce a sidebar with interactive controls.
# Sidebar filters
st.sidebar.header("Dashboard Filters")
# Dropdown for region selection
selected_regions = st.sidebar.multiselect(
"Select Regions",
options=sample_data["Region"].unique(),
default=sample_data["Region"].unique(),
)
# Slider for minimum revenue
min_revenue = st.sidebar.slider(
"Minimum Revenue ($)",
min_value=0,
max_value=200000,
value=50000,
step=5000,
)
# Date range picker
import datetime
date_range = st.sidebar.date_input(
"Date Range",
value=(datetime.date(2026, 1, 1), datetime.date(2026, 4, 7)),
min_value=datetime.date(2025, 1, 1),
max_value=datetime.date(2026, 12, 31),
)
# Text input for search
search_term = st.sidebar.text_input("Search by keyword", placeholder="e.g. North")
# Apply filters to the DataFrame
filtered_data = sample_data[
(sample_data["Region"].isin(selected_regions))
& (sample_data["Revenue"] >= min_revenue)
]
if search_term:
filtered_data = filtered_data[
filtered_data["Region"].str.contains(search_term, case=False)
]
st.subheader(f"Filtered Results ({len(filtered_data)} regions)")
st.dataframe(filtered_data, use_container_width=True)
# Radio button for chart type
chart_type = st.radio(
"Select visualization",
options=["Bar Chart", "Pie Chart", "Table Only"],
horizontal=True,
)
if chart_type == "Bar Chart":
st.bar_chart(filtered_data.set_index("Region")["Revenue"])
elif chart_type == "Pie Chart":
import plotly.express as px
fig = px.pie(filtered_data, values="Revenue", names="Region", title="Revenue Share")
st.plotly_chart(fig, use_container_width=True)
Here is what happens behind the scenes: when a user moves the slider, Streamlit reruns the entire script from top to bottom, but min_revenue now holds the new slider value. The filtered DataFrame updates, the table re-renders, and the chart reflects the new selection – all in a single pass. This execution model is called the dataflow paradigm, and it is the core architectural decision that makes streamlit python so approachable.
Streamlit 1.55.0 introduced the bind parameter on widgets, which synchronizes widget state with URL query parameters. For example, adding bind="region" to the multiselect widget would encode the selected regions in the URL, making the dashboard state shareable via a link. This is a massive usability improvement for teams that share dashboard links in Slack or email. The on_change callback, now available on containers like st.tabs and st.expander, lets you trigger specific logic without a full rerun – useful for logging analytics events or updating only a portion of the UI.
A few tips for widget design: place filters in the sidebar to keep the main area clean; use st.columns to arrange related widgets horizontally; and always provide sensible default values so the dashboard loads with meaningful data. If you are building forms with multiple inputs, wrap them in st.form to batch all inputs and submit them with a single button, which prevents unnecessary reruns.
Step 4 – Load and Transform Data with Pandas Integration
Real dashboards pull data from files, APIs, or databases – not from hardcoded dictionaries. In this step, we will create a realistic sales dataset, load it into our streamlit dashboard, and apply common transformations. Start by generating a CSV file that simulates 12 months of sales data across five regions.
# utils/helpers.py — Data generation and loading utilities
import pandas as pd
import numpy as np
from pathlib import Path
DATA_DIR = Path(__file__).parent.parent / "data"
def generate_sample_data(rows: int = 500) -> pd.DataFrame:
"""Generate a realistic sales dataset for the dashboard."""
np.random.seed(42)
regions = ["North", "South", "East", "West", "Central"]
products = ["Widget A", "Widget B", "Gadget X", "Gadget Y", "Service Plan"]
data = pd.DataFrame({
"date": pd.date_range("2025-01-01", periods=rows, freq="D"),
"region": np.random.choice(regions, size=rows),
"product": np.random.choice(products, size=rows),
"units_sold": np.random.randint(10, 500, size=rows),
"unit_price": np.random.uniform(15.0, 150.0, size=rows).round(2),
})
data["revenue"] = (data["units_sold"] * data["unit_price"]).round(2)
data["month"] = data["date"].dt.to_period("M").astype(str)
data["quarter"] = data["date"].dt.to_period("Q").astype(str)
return data
def save_sample_data() -> Path:
"""Save generated data to CSV."""
DATA_DIR.mkdir(exist_ok=True)
filepath = DATA_DIR / "sales_data.csv"
df = generate_sample_data()
df.to_csv(filepath, index=False)
return filepath
def load_data(filepath: str | Path) -> pd.DataFrame:
"""Load and validate a CSV dataset."""
df = pd.read_csv(filepath, parse_dates=["date"])
required_cols = {"date", "region", "product", "units_sold", "unit_price", "revenue"}
if not required_cols.issubset(df.columns):
missing = required_cols - set(df.columns)
raise ValueError(f"Missing columns: {missing}")
return df
def compute_summary(df: pd.DataFrame, group_col: str = "region") -> pd.DataFrame:
"""Aggregate revenue and units by a grouping column."""
summary = (
df.groupby(group_col)
.agg(
total_revenue=("revenue", "sum"),
total_units=("units_sold", "sum"),
avg_price=("unit_price", "mean"),
order_count=("revenue", "count"),
)
.round(2)
.sort_values("total_revenue", ascending=False)
.reset_index()
)
return summary
Now update app.py to use these utilities. Replace the hardcoded sample_data DataFrame with a call to load_data. If the CSV does not exist yet, the app generates it on first run.
# At the top of app.py, add these imports
from utils.helpers import generate_sample_data, save_sample_data, load_data, compute_summary
from pathlib import Path
# Load or generate data
data_path = Path("data/sales_data.csv")
if not data_path.exists():
save_sample_data()
df = load_data(data_path)
# Display raw data in an expandable section
with st.expander("View Raw Data", expanded=False):
st.dataframe(df, use_container_width=True, height=400)
st.caption(f"Showing {len(df):,} rows and {len(df.columns)} columns")
# Compute and display summary
summary = compute_summary(df, group_col="region")
st.subheader("Revenue by Region")
st.dataframe(
summary.style.format({
"total_revenue": "${:,.2f}",
"avg_price": "${:,.2f}",
"total_units": "{:,}",
"order_count": "{:,}",
}),
use_container_width=True,
)
Pandas integration is first-class in streamlit python. The st.dataframe widget renders any DataFrame with built-in sorting, searching, and column resizing. For static display, st.table renders a fixed HTML table. For editing, st.data_editor turns a DataFrame into an editable spreadsheet – users can modify cells, add rows, and delete rows, and the changes flow back into your script as a new DataFrame. This is powerful for internal tools where analysts need to override model predictions or correct data entry errors.
When your datasets grow beyond a few thousand rows, performance matters. Streamlit re-executes the entire script on every interaction, so loading a large CSV on each rerun would be painfully slow. The solution is caching, which we cover in Step 7. For now, understand that separating data-loading logic into utility functions (as we did in helpers.py) makes it trivial to add a @st.cache_data decorator later. If you are pulling data from web APIs, our Python web scraping tutorial covers techniques for fetching and parsing remote data sources.
Step 5 – Create Charts and Visualizations with Plotly
Data tables tell the story, but charts make it memorable. Streamlit supports multiple charting libraries out of the box – Altair (built-in), Matplotlib, Plotly, Bokeh, and even deck.gl for geospatial data. For interactive dashboards, Plotly is the strongest choice because it supports hover tooltips, zoom, pan, and export – all client-side, with no extra server load.
Create the visualizations page of our multi-page dashboard by adding the following code to pages/2_Visualizations.py.
# pages/2_Visualizations.py
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
from utils.helpers import load_data, compute_summary
from pathlib import Path
st.set_page_config(page_title="Visualizations", layout="wide")
st.title("Charts and Visualizations")
# Load data
df = load_data(Path("data/sales_data.csv"))
# --- Revenue by Region (Bar Chart) ---
st.subheader("Revenue by Region")
summary = compute_summary(df, "region")
fig_bar = px.bar(
summary,
x="region",
y="total_revenue",
color="region",
text_auto="$.2s",
title="Total Revenue by Region",
labels={"total_revenue": "Revenue ($)", "region": "Region"},
)
fig_bar.update_layout(showlegend=False, height=450)
st.plotly_chart(fig_bar, use_container_width=True)
# --- Monthly Trend (Line Chart) ---
st.subheader("Monthly Revenue Trend")
monthly = df.groupby("month")["revenue"].sum().reset_index()
fig_line = px.line(
monthly,
x="month",
y="revenue",
markers=True,
title="Monthly Revenue Trend",
labels={"revenue": "Revenue ($)", "month": "Month"},
)
fig_line.update_traces(line=dict(width=3))
st.plotly_chart(fig_line, use_container_width=True)
# --- Product Mix (Pie Chart) ---
col1, col2 = st.columns(2)
with col1:
st.subheader("Revenue by Product")
product_summary = compute_summary(df, "product")
fig_pie = px.pie(
product_summary,
values="total_revenue",
names="product",
hole=0.4,
title="Product Revenue Share",
)
st.plotly_chart(fig_pie, use_container_width=True)
with col2:
st.subheader("Units Sold Distribution")
fig_hist = px.histogram(
df,
x="units_sold",
nbins=30,
title="Distribution of Units Sold per Transaction",
labels={"units_sold": "Units Sold", "count": "Frequency"},
)
st.plotly_chart(fig_hist, use_container_width=True)
# --- Scatter Plot: Price vs Units ---
st.subheader("Price vs Units Sold")
fig_scatter = px.scatter(
df,
x="unit_price",
y="units_sold",
color="region",
size="revenue",
hover_data=["product", "date"],
title="Unit Price vs Units Sold (bubble size = revenue)",
labels={"unit_price": "Unit Price ($)", "units_sold": "Units Sold"},
)
fig_scatter.update_layout(height=500)
st.plotly_chart(fig_scatter, use_container_width=True)
Each st.plotly_chart call embeds a fully interactive Plotly figure in the page. Users can hover over data points to see exact values, drag to zoom into a specific range, double-click to reset the view, and click the camera icon to download the chart as a PNG. These interactions happen entirely in the browser – no server round-trips required.
A few best practices for streamlit dashboard visualizations: always set use_container_width=True so charts scale to the available space; use st.columns to place related charts side by side; add meaningful axis labels and titles so charts are self-explanatory; and keep the color palette consistent across all charts by using the same color_discrete_sequence parameter. For advanced chart customization, Plotly’s go.Figure API gives you pixel-level control over every visual element. The Streamlit API reference documents all supported chart types and their parameters.
Step 6 – Build a Multi-Page Dashboard Layout
As your streamlit dashboard grows beyond a single screen, multi-page navigation becomes essential. Streamlit’s built-in multi-page architecture uses a convention: any .py file placed in a pages/ directory at the same level as your entry-point script automatically appears in the sidebar as a separate page. The page ordering follows the filename prefix, so naming files with numbers (e.g., 1_Data_Explorer.py, 2_Visualizations.py) gives you explicit control over the navigation order.
Create the Data Explorer page at pages/1_Data_Explorer.py:
# pages/1_Data_Explorer.py
import streamlit as st
import pandas as pd
from utils.helpers import load_data
from pathlib import Path
st.set_page_config(page_title="Data Explorer", layout="wide")
st.title("Data Explorer")
df = load_data(Path("data/sales_data.csv"))
# Sidebar filters specific to this page
st.sidebar.header("Explorer Filters")
regions = st.sidebar.multiselect(
"Regions", options=df["region"].unique(), default=df["region"].unique()
)
products = st.sidebar.multiselect(
"Products", options=df["product"].unique(), default=df["product"].unique()
)
date_min, date_max = st.sidebar.date_input(
"Date Range",
value=(df["date"].min().date(), df["date"].max().date()),
)
# Apply filters
mask = (
df["region"].isin(regions)
& df["product"].isin(products)
& (df["date"].dt.date >= date_min)
& (df["date"].dt.date <= date_max)
)
filtered = df[mask]
# KPI row
c1, c2, c3, c4 = st.columns(4)
c1.metric("Rows", f"{len(filtered):,}")
c2.metric("Revenue", f"${filtered['revenue'].sum():,.0f}")
c3.metric("Avg Price", f"${filtered['unit_price'].mean():,.2f}")
c4.metric("Avg Units", f"{filtered['units_sold'].mean():,.0f}")
# Editable DataFrame
st.subheader("Interactive Data Table")
edited_df = st.data_editor(
filtered,
use_container_width=True,
height=500,
num_rows="dynamic",
)
# Download button
csv = edited_df.to_csv(index=False).encode("utf-8")
st.download_button(
label="Download Filtered Data as CSV",
data=csv,
file_name="filtered_sales_data.csv",
mime="text/csv",
)
# Quick stats in tabs
tab1, tab2, tab3 = st.tabs(["By Region", "By Product", "By Quarter"])
with tab1:
st.dataframe(
filtered.groupby("region")["revenue"].agg(["sum", "mean", "count"])
.round(2).sort_values("sum", ascending=False),
use_container_width=True,
)
with tab2:
st.dataframe(
filtered.groupby("product")["revenue"].agg(["sum", "mean", "count"])
.round(2).sort_values("sum", ascending=False),
use_container_width=True,
)
with tab3:
st.dataframe(
filtered.groupby("quarter")["revenue"].agg(["sum", "mean", "count"])
.round(2).sort_values("sum", ascending=False),
use_container_width=True,
)
Streamlit 1.55.0 added the on_change parameter to st.tabs, which fires a callback whenever the user switches between tabs. This is useful for lazy-loading expensive computations – rather than computing all three tab contents upfront, you can compute only the active tab’s data. The st.data_editor widget deserves special attention: it turns any DataFrame into a spreadsheet-like editor where users can modify values, add new rows, and delete existing ones. The edited data is returned as a new DataFrame, which you can save back to a database or file.
The st.download_button widget generates a client-side download link for any binary or text data. Users click it and immediately receive the file – no server-side file storage required. This is invaluable for dashboards where analysts need to export filtered data for further analysis in Excel or share it with stakeholders who do not have access to the dashboard. Combined with the editable table, you have a complete data review and export workflow in fewer than 80 lines of code.
For navigation, each page operates as an independent Streamlit script with its own st.set_page_config call. Session state (covered in Step 9) is shared across pages, so you can pass user preferences, authentication tokens, or cached data between pages without external storage. If you need more control over the navigation structure – nested pages, custom icons, or conditional visibility – the st.navigation API introduced in recent versions lets you define the sidebar programmatically.
Step 7 – Add Real-Time Data Updates and Caching
Performance is the silent killer of streamlit dashboards. Because Streamlit reruns your entire script on every interaction, any slow operation – a database query, an API call, a heavy computation – will freeze the UI. Streamlit solves this with two caching decorators: @st.cache_data for serializable data (DataFrames, strings, numbers) and @st.cache_resource for non-serializable objects (database connections, ML models, API clients).
# Caching examples — add to utils/helpers.py
import streamlit as st
import time
@st.cache_data(ttl=300) # Cache for 5 minutes
def load_data_cached(filepath: str) -> pd.DataFrame:
"""Load CSV with caching — only re-reads when the file changes or TTL expires."""
df = pd.read_csv(filepath, parse_dates=["date"])
return df
@st.cache_data(ttl=60)
def fetch_live_metrics() -> dict:
"""Simulate fetching live metrics from an API."""
# In production, replace with: requests.get("https://api.example.com/metrics").json()
time.sleep(0.5) # Simulate network latency
return {
"active_users": np.random.randint(800, 1200),
"requests_per_second": np.random.randint(150, 400),
"error_rate": round(np.random.uniform(0.1, 2.5), 2),
"avg_response_ms": np.random.randint(45, 200),
}
@st.cache_resource
def get_database_connection():
"""Create a database connection that persists across reruns."""
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="dashboard_db",
user="dashboard_user",
password=st.secrets["db_password"],
)
return conn
The ttl (time-to-live) parameter controls how long cached results remain valid. Setting ttl=300 means the function will re-execute after five minutes, regardless of whether the inputs changed. This is perfect for API data that updates periodically. For file-based data, @st.cache_data automatically invalidates the cache when the function’s input arguments change – so if you pass a different file path, Streamlit loads the new file.
For true real-time updates, Streamlit provides st.empty containers and the st.rerun() function. Here is a pattern for a live-updating metrics panel:
# Real-time metrics panel — add to app.py
import time
st.subheader("Live Metrics")
placeholder = st.empty()
# Auto-refresh toggle
auto_refresh = st.checkbox("Enable auto-refresh (every 10 seconds)")
if auto_refresh:
while True:
metrics = fetch_live_metrics()
with placeholder.container():
m1, m2, m3, m4 = st.columns(4)
m1.metric("Active Users", f"{metrics['active_users']:,}")
m2.metric("Requests/sec", metrics["requests_per_second"])
m3.metric("Error Rate", f"{metrics['error_rate']}%")
m4.metric("Avg Response", f"{metrics['avg_response_ms']} ms")
time.sleep(10)
st.rerun()
else:
metrics = fetch_live_metrics()
with placeholder.container():
m1, m2, m3, m4 = st.columns(4)
m1.metric("Active Users", f"{metrics['active_users']:,}")
m2.metric("Requests/sec", metrics["requests_per_second"])
m3.metric("Error Rate", f"{metrics['error_rate']}%")
m4.metric("Avg Response", f"{metrics['avg_response_ms']} ms")
The st.empty() container creates a single-element placeholder that you can overwrite on each iteration. Combined with st.rerun(), this gives you a polling-based live update mechanism. For production use cases where sub-second latency matters, consider using Streamlit’s st.connection API with a streaming data source like Kafka or WebSockets.
A common caching mistake is decorating functions that depend on Streamlit widgets. Since widget values change on every interaction, the cache key changes too, and you get zero cache hits. The fix is to pass only the data-identifying parameters (file path, query string, date range) to the cached function, and apply widget-based filtering after the cached data is loaded. This pattern – cache the heavy load, filter the light result – is the single most important performance optimization for any streamlit python application.
Step 8 – Style Your Dashboard with Custom Themes
Default Streamlit apps look clean, but a custom theme aligns your dashboard with your brand identity and improves readability. Streamlit 1.55.0 supports simultaneous light and dark theme configuration, so your app looks polished regardless of the user’s system preference. Theme settings live in .streamlit/config.toml.
# .streamlit/config.toml
[theme]
primaryColor = "#4F8BF9"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F0F2F6"
textColor = "#262730"
font = "sans serif"
[server]
port = 8501
headless = true
maxUploadSize = 200
[browser]
gatherUsageStats = false
The primaryColor controls the accent color for widgets – sliders, checkboxes, links, and active tab indicators all inherit this value. The secondaryBackgroundColor sets the sidebar background and the background of input widgets on the main page. For dark mode, create a separate [theme.dark] section (available in 1.55.0) with contrasting values.
Beyond the TOML configuration, you can inject custom CSS using st.markdown with the unsafe_allow_html flag. This is useful for fine-tuning spacing, hiding default elements, or adding custom fonts.
# Custom CSS — add near the top of app.py, after st.set_page_config
st.markdown("""
<style>
/* Hide the default Streamlit hamburger menu and footer */
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
/* Custom metric card styling */
div[data-testid="stMetric"] {
background-color: #f0f2f6;
border-radius: 8px;
padding: 16px;
border-left: 4px solid #4F8BF9;
}
/* Wider sidebar */
[data-testid="stSidebar"] {
min-width: 280px;
max-width: 320px;
}
/* Custom header font size */
h2 {
color: #1E3A5F;
}
</style>
""", unsafe_allow_html=True)
A word of caution: Streamlit’s internal DOM structure is not a public API, so selectors like div[data-testid="stMetric"] may break between versions. Test your custom CSS after every Streamlit upgrade. For reliable, maintainable styling, prefer the TOML theme configuration over CSS injection wherever possible.
Custom components offer another styling avenue. Streamlit 1.51.0 introduced Custom Components v2 with frameless UI and bidirectional data flow, allowing you to embed React or vanilla JavaScript widgets that look native to the Streamlit interface. If you need a custom chart type, a drag-and-drop interface, or a branded header component, Custom Components v2 is the way to build it. The architecture uses message passing between the Python host and the JavaScript component, with full support for sending DataFrames and receiving user events. For teams that also work with CSS frameworks, our Tailwind CSS dashboard tutorial covers complementary front-end techniques that you might find applicable when building custom Streamlit components.
Step 9 – Add Authentication and Session State
Most internal dashboards require some form of access control. While Streamlit does not include a built-in authentication system, st.session_state provides the persistence layer you need to build one. Session state is a dictionary-like object that survives across reruns within a single browser session – exactly what you need for login flows.
# auth.py — Simple authentication module
import streamlit as st
import hashlib
# In production, use a proper user database and bcrypt hashing
USERS = {
"admin": hashlib.sha256("securepass123".encode()).hexdigest(),
"analyst": hashlib.sha256("viewonly456".encode()).hexdigest(),
"manager": hashlib.sha256("reports789".encode()).hexdigest(),
}
ROLES = {
"admin": ["view", "edit", "delete", "export"],
"analyst": ["view", "export"],
"manager": ["view", "export"],
}
def check_credentials(username: str, password: str) -> bool:
"""Validate username and password."""
if username in USERS:
return USERS[username] == hashlib.sha256(password.encode()).hexdigest()
return False
def login_page():
"""Render the login form."""
st.title("Login")
with st.form("login_form"):
username = st.text_input("Username")
password = st.text_input("Password", type="password")
submitted = st.form_submit_button("Sign In")
if submitted:
if check_credentials(username, password):
st.session_state["authenticated"] = True
st.session_state["username"] = username
st.session_state["role"] = ROLES.get(username, ["view"])
st.rerun()
else:
st.error("Invalid username or password.")
def require_auth():
"""Gate that blocks unauthenticated access."""
if not st.session_state.get("authenticated", False):
login_page()
st.stop()
def logout_button():
"""Render a logout button in the sidebar."""
if st.sidebar.button("Logout"):
for key in ["authenticated", "username", "role"]:
st.session_state.pop(key, None)
st.rerun()
Now integrate authentication into app.py by calling require_auth() at the top of the script, immediately after st.set_page_config. If the user is not authenticated, the login form renders and st.stop() halts the rest of the script. Once logged in, the session state persists the username and role across all pages and reruns.
# Add to the top of app.py (after set_page_config)
from auth import require_auth, logout_button
require_auth()
logout_button()
# Show user info in sidebar
st.sidebar.success(f"Logged in as: {st.session_state['username']}")
st.sidebar.info(f"Permissions: {', '.join(st.session_state['role'])}")
# ... rest of dashboard code ...
Session state also powers multi-step workflows like wizards, shopping carts, and form chains. Any value stored in st.session_state persists until the browser tab is closed or the user explicitly clears it. Since session state is server-side memory, it does not survive server restarts – for durable persistence, write to a database. For production authentication, consider streamlit-authenticator, a third-party component that handles password hashing with bcrypt, cookie-based remember-me tokens, and password reset flows. If your organization uses SSO, you can integrate with OAuth2 providers using the st.experimental_user API on Streamlit Community Cloud or a reverse proxy like Authelia in self-hosted deployments.
A practical tip: use session state to store expensive computation results. If a user triggers a heavy report generation on one page, store the result in st.session_state["report_data"] and access it from another page without recomputation. This is especially useful when combined with the RAG chatbot patterns where you may want to persist conversation history across dashboard navigation.
Step 10 – Connect to a Database (PostgreSQL and SQLite)
Production dashboards rarely run on CSV files. In this step, we connect our streamlit dashboard to a database using Streamlit’s st.connection API and raw psycopg2. We will support both PostgreSQL (for production) and SQLite (for local development).
First, configure your database credentials using Streamlit’s secrets management. Create a file at .streamlit/secrets.toml (never commit this file to version control):
# .streamlit/secrets.toml
[connections.postgresql]
dialect = "postgresql"
host = "localhost"
port = 5432
database = "dashboard_db"
username = "dashboard_user"
password = "your_secure_password"
[connections.sqlite]
url = "sqlite:///data/dashboard.db"
Now create the database page at pages/3_Database.py:
# pages/3_Database.py
import streamlit as st
import pandas as pd
import sqlite3
from pathlib import Path
st.set_page_config(page_title="Database", layout="wide")
st.title("Database Explorer")
# SQLite setup for local development
DB_PATH = Path("data/dashboard.db")
@st.cache_resource
def get_sqlite_connection():
"""Create a persistent SQLite connection."""
DB_PATH.parent.mkdir(exist_ok=True)
conn = sqlite3.connect(str(DB_PATH), check_same_thread=False)
return conn
def init_database(conn):
"""Initialize the database with sample data if empty."""
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS sales (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date TEXT NOT NULL,
region TEXT NOT NULL,
product TEXT NOT NULL,
units_sold INTEGER NOT NULL,
unit_price REAL NOT NULL,
revenue REAL NOT NULL
)
""")
# Check if data exists
count = cursor.execute("SELECT COUNT(*) FROM sales").fetchone()[0]
if count == 0:
# Load from CSV
df = pd.read_csv("data/sales_data.csv")
df.to_sql("sales", conn, if_exists="append", index=False)
conn.commit()
st.toast(f"Loaded {len(df)} rows into the database.")
return count
conn = get_sqlite_connection()
row_count = init_database(conn)
# Query builder
st.subheader("SQL Query Editor")
default_query = """SELECT region, SUM(revenue) as total_revenue, COUNT(*) as orders
FROM sales
GROUP BY region
ORDER BY total_revenue DESC"""
query = st.text_area("Enter SQL query:", value=default_query, height=120)
if st.button("Run Query", type="primary"):
try:
result = pd.read_sql_query(query, conn)
st.dataframe(result, use_container_width=True)
st.caption(f"Query returned {len(result)} rows")
# Download query results
csv = result.to_csv(index=False).encode("utf-8")
st.download_button("Download Results", csv, "query_results.csv", "text/csv")
except Exception as e:
st.error(f"Query error: {e}")
# Pre-built reports
st.subheader("Quick Reports")
report_type = st.selectbox(
"Select a report",
["Top Products by Revenue", "Monthly Trends", "Regional Comparison", "Price Analysis"]
)
report_queries = {
"Top Products by Revenue": """
SELECT product, SUM(revenue) as total_revenue,
SUM(units_sold) as total_units,
ROUND(AVG(unit_price), 2) as avg_price
FROM sales GROUP BY product ORDER BY total_revenue DESC
""",
"Monthly Trends": """
SELECT substr(date, 1, 7) as month,
SUM(revenue) as revenue,
SUM(units_sold) as units
FROM sales GROUP BY month ORDER BY month
""",
"Regional Comparison": """
SELECT region, COUNT(*) as transactions,
SUM(revenue) as total_revenue,
ROUND(AVG(revenue), 2) as avg_transaction
FROM sales GROUP BY region ORDER BY total_revenue DESC
""",
"Price Analysis": """
SELECT product, region,
ROUND(MIN(unit_price), 2) as min_price,
ROUND(AVG(unit_price), 2) as avg_price,
ROUND(MAX(unit_price), 2) as max_price
FROM sales GROUP BY product, region ORDER BY product, region
""",
}
report_df = pd.read_sql_query(report_queries[report_type], conn)
st.dataframe(report_df, use_container_width=True)
For PostgreSQL in production, replace the SQLite connection with st.connection("postgresql"), which reads credentials from secrets.toml automatically. The @st.cache_resource decorator ensures the connection object is created once and reused across reruns, avoiding the overhead of establishing a new connection on every interaction. Always use parameterized queries in production to prevent SQL injection – the example above is for internal tools where the user is trusted.
Streamlit’s secrets management works differently depending on your deployment target. Locally, secrets live in .streamlit/secrets.toml. On Streamlit Community Cloud, you enter secrets through the web UI. In Docker, you pass them as environment variables. And in Snowflake (Streamlit in Snowflake integration, compatible with version 1.52.1), secrets are managed through Snowflake’s native secrets manager. This flexibility means your database connection code stays the same across all environments – only the secrets delivery mechanism changes.
Step 11 – Deploy to Streamlit Community Cloud
Streamlit Community Cloud is the fastest path from code to a live URL. It is free for public apps and requires nothing more than a GitHub repository. Here is the deployment checklist.
First, make sure your project is in a GitHub repository with the following files at the root: app.py (or whatever your entry point is), requirements.txt, and optionally .streamlit/config.toml for theme settings. Do not include .streamlit/secrets.toml – secrets are entered through the Community Cloud web interface.
- Push your project to a public GitHub repository.
- Visit streamlit.io/cloud and sign in with your GitHub account.
- Click “New app” and select your repository, branch, and entry-point file.
- Under “Advanced settings,” paste the contents of your
secrets.tomlfor database credentials. - Click “Deploy” and wait 2-3 minutes for the build to complete.
- Your app is live at
https://your-app-name.streamlit.app.
Community Cloud installs dependencies from requirements.txt automatically. If you need system-level packages (like libpq-dev for PostgreSQL), create a packages.txt file at the project root with one package name per line. For Python version selection, add a .python-version file or a runtime.txt containing the version number (e.g., 3.12).
Community Cloud apps go to sleep after a period of inactivity and wake up when a user visits the URL. The cold start takes 30-60 seconds, which is acceptable for internal tools but not ideal for customer-facing dashboards. If uptime matters, the paid Streamlit for Teams plan offers always-on deployments, private app sharing, and viewer authentication. Alternatively, deploy to your own infrastructure using Docker (next step) or as an ASGI app mounted inside a FastAPI server – a pattern enabled by the experimental st.App entry point in Streamlit 1.55.0.
A few Community Cloud deployment tips: keep your requirements.txt lean to reduce build times; avoid including large data files in the repository (use cloud storage or a database instead); and test your app with streamlit run app.py --server.headless true locally before deploying to catch any issues with file paths or missing dependencies. The official Streamlit documentation provides a complete reference for all deployment options and configuration parameters.
Step 12 – Deploy with Docker for Production
For enterprise deployments where you need full control over the infrastructure, Docker is the standard approach. A containerized streamlit dashboard runs identically on any machine – your laptop, a staging server, or a Kubernetes cluster. If you are new to containerization, our Docker tutorial for beginners covers the fundamentals.
# Dockerfile
FROM python:3.12-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends
build-essential
libpq-dev
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose the Streamlit port
EXPOSE 8501
# Health check
HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1
# Run Streamlit
ENTRYPOINT ["streamlit", "run", "app.py",
"--server.port=8501",
"--server.address=0.0.0.0",
"--server.headless=true",
"--browser.gatherUsageStats=false"]
Build and run the container:
# Build the Docker image
docker build -t streamlit-dashboard:latest .
# Run the container
docker run -d
--name dashboard
-p 8501:8501
-e DB_HOST=host.docker.internal
-e DB_PASSWORD=your_secure_password
--restart unless-stopped
streamlit-dashboard:latest
# Check logs
docker logs -f dashboard
Several production considerations deserve attention. First, the python:3.12-slim base image is about 120 MB – much smaller than the full Python image. Second, copying requirements.txt before the application code enables Docker layer caching: if your dependencies have not changed, Docker skips the pip install step entirely on subsequent builds. Third, the HEALTHCHECK directive hits Streamlit’s internal health endpoint, which lets orchestrators like Kubernetes or Docker Swarm detect and restart unhealthy containers.
For secrets management in Docker, avoid baking credentials into the image. Instead, pass them as environment variables at runtime (as shown above) and read them in your Streamlit app using os.environ. On Kubernetes, use Secrets or a vault integration like HashiCorp Vault. Streamlit’s st.secrets API reads from .streamlit/secrets.toml by default, but you can override this by setting the STREAMLIT_SECRETS_TOML environment variable to a custom path.
The experimental ASGI-compatible st.App entry point in Streamlit 1.55.0 opens another deployment path: mounting your Streamlit dashboard as a sub-application inside a FastAPI or Starlette server. This means you can serve both a REST API and a dashboard from the same process, sharing database connections and business logic. For teams already running FastAPI in production, this eliminates the need for a separate Streamlit deployment entirely.
Streamlit Python vs Dash vs Gradio: Framework Comparison Table
Choosing the right dashboard framework depends on your use case. Here is a detailed comparison of the three most popular Python dashboard frameworks as of 2026.
| Feature | Streamlit 1.55 | Dash 2.18 | Gradio 5.x |
|---|---|---|---|
| Primary use case | Data dashboards, internal tools | Enterprise analytics apps | ML model demos |
| Learning curve | Low – pure Python | Medium – callback-based | Low – decorator-based |
| Execution model | Top-to-bottom rerun | Reactive callbacks | Event-driven |
| Custom CSS/HTML | Limited (unsafe_allow_html) | Full control | Theme-based |
| Multi-page support | Built-in (pages/ folder) | Built-in (dash.page_registry) | Tabs and Blocks |
| Free cloud hosting | Streamlit Community Cloud | No (Dash Enterprise is paid) | Hugging Face Spaces |
| Real-time updates | Polling via st.rerun | WebSocket via dash-extensions | Streaming via generators |
| ASGI integration | Experimental (st.App) | Native (Flask/WSGI) | Native (FastAPI mount) |
| GitHub stars (2026) | 38,000+ | 22,000+ | 35,000+ |
| Best for | Data teams, prototyping | Pixel-perfect enterprise apps | ML demos, Hugging Face ecosystem |
Streamlit wins on developer experience and speed-to-deployment. The top-to-bottom execution model is intuitive for data scientists who think in scripts, not callbacks. Dash offers more layout control and is better suited for pixel-perfect enterprise applications where every widget position matters. Gradio is the natural choice for ML model demonstrations, especially if you are already working within the Hugging Face ecosystem.
For most data dashboard use cases – the kind covered in this streamlit tutorial – Streamlit is the right choice. Its caching system, built-in widgets, and free Community Cloud deployment make it the fastest path from a Python script to a shareable web application. If you later outgrow Streamlit’s layout constraints, your Pandas and Plotly code transfers directly to Dash with minimal rewriting. For those exploring broader performance trade-offs in the Python ecosystem, our Python vs Rust comparison examines when it makes sense to reach for a systems language.
8 Common Pitfalls and How to Avoid Them
After reviewing hundreds of streamlit examples across open-source projects and enterprise deployments, these are the mistakes that trip up developers most frequently.
Pitfall 1: Calling st.set_page_config after other Streamlit commands. This function must be the very first Streamlit call in your script. Even an st.write or st.sidebar call before it will raise a StreamlitAPIException. Move it to line 1, right after your imports.
Pitfall 2: Loading large datasets on every rerun without caching. A 500 MB CSV file takes seconds to parse on each interaction. Wrap your data-loading function with @st.cache_data to load it once and reuse the cached result. Set a ttl if the underlying data changes periodically.
Pitfall 3: Using global variables for state management. Global variables reset on every rerun because Streamlit re-executes the script from scratch. Use st.session_state instead. For example, counter += 1 as a global variable will always equal 1, but st.session_state.counter += 1 persists across reruns.
Pitfall 4: Creating database connections inside the main script body. Each rerun opens a new connection, eventually exhausting the connection pool. Use @st.cache_resource to create the connection once, or use st.connection which handles pooling automatically.
Pitfall 5: Forgetting to add a requirements.txt for deployment. Streamlit Community Cloud and Docker both depend on this file. If it is missing or incomplete, your app deploys with missing dependencies and crashes on import. Always test with a fresh virtual environment before deploying.
Pitfall 6: Nesting st.columns inside st.columns. While technically possible, deeply nested column layouts often produce unreadable UIs on smaller screens. Stick to a single level of columns and use st.tabs or st.expander for additional organization.
Pitfall 7: Ignoring the rerun model when using loops. A while True loop inside a Streamlit script blocks all other interactions. Use st.rerun() with a timer or st.empty() containers for periodic updates, and always provide a way to break the loop.
Pitfall 8: Committing secrets.toml to version control. This file contains database passwords and API keys. Add .streamlit/secrets.toml to your .gitignore immediately. For Streamlit Community Cloud, enter secrets through the web UI instead.
Troubleshooting: 10 Errors Every Streamlit Developer Hits
When building your streamlit dashboard, you will inevitably encounter errors. Here are the ten most common issues along with their root causes and solutions.
| Error | Root Cause | Solution |
|---|---|---|
StreamlitAPIException: set_page_config() can only be called once per app page | Another st.* call appears before set_page_config | Move st.set_page_config to the first line after imports. |
ModuleNotFoundError: No module named 'streamlit' | Virtual environment not activated, or Streamlit not installed | Run source .venv/bin/activate then pip install streamlit. |
DuplicateWidgetID: There are multiple widgets with the same key | Two widgets have identical labels or key parameters | Add a unique key parameter to each widget, e.g., key="slider_1". |
CachedStFunctionWarning: Your script uses st.* commands inside a cached function | A @st.cache_data function calls st.write or other display commands | Move st.* calls outside the cached function. Cached functions should return data only. |
UnhashableTypeError: Cannot hash argument of type sqlite3.Connection | @st.cache_data cannot serialize a database connection | Use @st.cache_resource for non-serializable objects like connections and models. |
PermissionError: [Errno 13] Permission denied: '.streamlit/secrets.toml' | File permissions are too restrictive | Run chmod 600 .streamlit/secrets.toml on macOS/Linux. |
| Blank page with no errors in the terminal | JavaScript error in a custom component, or CORS issue behind a proxy | Check the browser developer console (F12) for JavaScript errors. Add CORS headers if using a reverse proxy. |
OSError: [Errno 98] Address already in use | Port 8501 is already occupied by another process | Kill the existing process with lsof -ti:8501 | xargs kill or use --server.port 8502. |
| Slow app with no visible cause | Missing cache decorators on expensive operations | Profile with st.spinner to identify slow sections, then add @st.cache_data or @st.cache_resource. |
FileNotFoundError on Community Cloud but works locally | Relative file paths resolve differently on the cloud server | Use Path(__file__).parent for paths relative to the script, not the working directory. |
When debugging streamlit python applications, the terminal where you ran streamlit run is your primary log source. All Python exceptions, warnings, and print statements appear there. For more verbose output, set the log level in your config: [logger] level = "debug" in .streamlit/config.toml. The browser’s developer tools (F12) show client-side errors – particularly useful when custom components or unsafe_allow_html content misbehaves.
If your app works locally but fails on Community Cloud, the most likely culprits are missing dependencies in requirements.txt, missing system packages in packages.txt, or file paths that assume a specific working directory. Community Cloud clones your repository into an arbitrary location, so always resolve paths relative to the script file using Path(__file__).parent. For detailed logs on Community Cloud, click the “Manage app” button in the bottom-right corner of your deployed app to access the build and runtime logs.
Another common issue involves st.session_state keys disappearing unexpectedly. This happens when a widget’s key parameter conflicts with a manually set session state key, or when a widget is conditionally rendered (present on one rerun, absent on the next). The rule of thumb: if a widget creates a session state key, do not manually overwrite it with st.session_state[key] = value. Use the widget’s default or value parameter instead.
Advanced Tips for Production Streamlit Dashboards
Once your streamlit dashboard is deployed and serving real users, these advanced techniques will help you scale, maintain, and monitor it effectively.
Tip 1: Use st.fragment for partial reruns. By default, Streamlit reruns the entire script when any widget changes. The @st.fragment decorator (stable in 1.55.0) marks a function as independently re-runnable – only the fragment re-executes, not the entire page. This is a major shift for dashboards with expensive initialization code that should not run on every slider move.
Tip 2: Profile your app with Streamlit’s built-in profiler. Add ?_profile=true to your app URL to see how much time each Streamlit command takes. Identify bottlenecks and add caching or fragment decorators where they will have the most impact.
Tip 3: Implement row-level security for multi-tenant dashboards. Use st.session_state["username"] to filter database queries so each user sees only their authorized data. Combine this with Streamlit Community Cloud’s built-in viewer authentication for a complete access control solution.
Tip 4: Pre-compute aggregations for large datasets. Instead of loading a 10-million-row table and aggregating in Pandas on every rerun, create materialized views in your database and query the pre-aggregated data. This reduces load time from seconds to milliseconds.
Tip 5: Use st.connection for managed connections. The st.connection API handles connection pooling, reconnection on failure, and secrets management automatically. It supports SQL databases, Snowflake, Google Sheets, and custom connection types. It is the recommended approach over raw psycopg2 or sqlalchemy connections.
Tip 6: Mount Streamlit as an ASGI sub-app. The experimental st.App entry point lets you mount Streamlit inside a FastAPI application. This is useful when your team already has a FastAPI service and wants to add a dashboard without deploying a separate process. The ASGI integration shares the event loop, so database connections and background tasks can be reused.
Tip 7: Use AI coding tools to accelerate development. Large language models can generate Streamlit layouts, Plotly charts, and Pandas transformations faster than you can type them. For teams exploring this workflow, our AI coding tools guide reviews the current landscape. You can also build LLM-powered features directly into your dashboard – the st.chat_input and st.chat_message widgets are specifically designed for conversational AI interfaces.
Tip 8: Monitor performance with application-level metrics. Log response times, error rates, and user actions to an observability platform like Datadog or Grafana. Streamlit does not expose Prometheus metrics natively, but you can add a middleware that logs timing data for each rerun. The ASGI integration makes this straightforward with standard ASGI middleware.
Related Coverage
If this streamlit tutorial was useful, these related guides cover complementary skills for building data-driven Python applications:
- Python Automation Tutorial – automate data pipelines, file processing, and scheduled tasks with Python.
- Python Web Scraping Tutorial – fetch and parse data from websites to feed into your dashboards.
- FastAPI Tutorial – build REST APIs that your Streamlit dashboard can consume or integrate with via ASGI.
- Flask Tutorial – the classic Python web framework for API development.
- RAG Chatbot Tutorial – add conversational AI features to your dashboard with LangChain.
- PyTorch Tutorial – train and deploy ML models that your dashboard can serve and monitor.
- Docker Tutorial – containerize any Python application for reliable production deployment.
- Django vs Flask – compare Python web frameworks for back-end development.
FAQ: Streamlit Python in 2026
What is Streamlit and what is it used for?
Streamlit is an open-source Python framework for building interactive web applications and data dashboards. It is used primarily by data scientists, ML engineers, and analysts to turn Python scripts into shareable web apps without writing any front-end code. Common use cases include data exploration tools, ML model demos, internal reporting dashboards, and real-time monitoring panels. Streamlit is trusted by more than 90% of Fortune 50 companies and has over 38,000 GitHub stars.
Is Streamlit free to use in 2026?
Yes. Streamlit is fully open-source under the Apache 2.0 license, which means it is free for both personal and commercial use. Streamlit Community Cloud offers free hosting for public applications. For private apps, teams, and enterprise features (SSO, viewer authentication, custom domains), Snowflake offers paid plans. You can also self-host Streamlit on your own servers or cloud infrastructure at no software cost – you only pay for the compute resources.
What Python version does Streamlit 1.55.0 require?
Streamlit 1.55.0 supports Python 3.9 through 3.14. Python 3.14 support was added in late 2025 to accommodate the latest language features. For the best experience, we recommend Python 3.12 or later, which offers improved performance and better error messages. You can check your Python version with python --version and install the latest Streamlit from PyPI with pip install streamlit==1.55.0.
Can I deploy a Streamlit app to production?
Absolutely. There are multiple production deployment options for streamlit python applications. Streamlit Community Cloud is the simplest – connect your GitHub repo and deploy in minutes. For enterprise environments, Docker containers are the standard approach, deployable to AWS ECS, Google Cloud Run, Azure Container Instances, or Kubernetes. The experimental st.App ASGI entry point in Streamlit 1.55.0 also lets you mount a Streamlit app inside a FastAPI or Starlette server. Streamlit in Snowflake is another option for organizations already using the Snowflake data platform.
How does Streamlit compare to Dash and Gradio?
Streamlit, Dash, and Gradio serve overlapping but distinct use cases. Streamlit excels at rapid prototyping and data dashboards with its intuitive top-to-bottom execution model. Dash (by Plotly) offers more layout control and is better for pixel-perfect enterprise applications but has a steeper learning curve due to its callback-based architecture. Gradio is optimized for ML model demonstrations and integrates tightly with the Hugging Face ecosystem. For most data dashboard projects, streamlit python offers the fastest development speed and the smoothest deployment experience.
What are the key new features in Streamlit 1.55.0?
Streamlit 1.55.0, released March 3, 2026, introduced several significant features. Dynamic containers like st.tabs, st.popover, and st.expander now support an on_change parameter for responding to user interactions without full reruns. Widget binding via the bind parameter syncs widget state with URL query parameters, making dashboard states bookmarkable. The experimental st.App ASGI-compatible entry point enables mounting Streamlit inside FastAPI or Starlette servers. These features build on earlier additions like Custom Components v2 (frameless UI, bidirectional data flow) in 1.51.0 and Streamlit in Snowflake compatibility in 1.52.1.
How do I handle large datasets in Streamlit?
Large datasets require a combination of caching, lazy loading, and database-side aggregation. Use @st.cache_data to avoid re-reading files on every interaction. For datasets that exceed available memory, query only the aggregated results from your database rather than loading raw rows into Pandas. The @st.fragment decorator limits reruns to specific sections of your app, preventing expensive data reloads when unrelated widgets change. For real-time data streams, use st.connection with a TTL-based cache so fresh data arrives periodically without blocking the UI.
Can I use Streamlit with machine learning models?
Yes, and this is one of streamlit python’s strongest use cases. Use @st.cache_resource to load your model once (whether it is a scikit-learn pipeline, a PyTorch neural network, or a Hugging Face transformer), and then call it on user-provided inputs. The st.chat_input and st.chat_message widgets enable conversational AI interfaces, and the st.file_uploader widget lets users upload images, CSVs, or other files for model inference. For a complete example of integrating LLMs with a Python application, see our RAG chatbot tutorial.
Nadia Dubois
Nadia Dubois is the AI & Innovation Editor at Tech Insider, where she tracks the rapid evolution of artificial intelligence, from foundation models to real-world enterprise deployment. She previously covered AI and startups for La Tribune and contributed to MIT Technology Review's European coverage. Nadia specializes in generative AI, AI regulation, and the intersection of technology and European industrial policy. She holds a dual degree in Computational Linguistics and Journalism from Sciences Po Paris.
View all articles