name: streamlit-expert description: Streamlit Web application development expert skill. Provides guidance on OIDC authentication (st.login/st.logout/st.user), secrets management, data visualization with Plotly/Altair, performance optimization with caching, and modern Streamlit features (v1.42-1.52+). Use this skill when building Streamlit apps, implementing user authentication, creating data dashboards, or optimizing app performance. Triggers include "streamlit app", "st.login", "data dashboard", "streamlit authentication", "streamlit visualization".
Streamlit Expert
Overview
Streamlit Web application development expert skill supporting the latest features from v1.42 to v1.52+ (2025-2026). Provides comprehensive guidance on:
- Authentication: Native OIDC authentication with
st.login(),st.logout(),st.user - Data Visualization: Optimal library selection (Plotly, Altair, native charts) and performance tuning
- Secrets Management: Secure credential handling with
st.secrets - Performance Optimization: Caching strategies, large dataset handling, session state management
- Modern Features: Custom themes, layout containers, multipage apps, Custom Components v2
When to Use This Skill
Use this skill when:
- Building new Streamlit applications from scratch
- Implementing user authentication with OIDC providers (Google, Microsoft, Okta, Auth0)
- Creating data visualization dashboards
- Optimizing Streamlit app performance
- Managing secrets and credentials securely
- Implementing modern Streamlit features (v1.42+)
Workflow Decision Tree
User Request
├── "Add authentication" → Authentication Workflow
├── "Create dashboard/visualization" → Visualization Workflow
├── "App is slow/optimize" → Performance Optimization Workflow
├── "New Streamlit app" → Project Setup Workflow
└── "Deploy app" → Deployment Workflow
1. Project Setup Workflow
Initial Project Structure
my-streamlit-app/
├── .streamlit/
│ ├── config.toml # App configuration
│ └── secrets.toml # Secrets (DO NOT COMMIT)
├── pages/ # Multipage app pages
│ ├── 1_Dashboard.py
│ └── 2_Settings.py
├── app.py # Main entry point
├── requirements.txt
├── .gitignore
└── README.md
Essential .gitignore Entries
# Streamlit secrets - CRITICAL
.streamlit/secrets.toml
# Python
__pycache__/
*.pyc
.venv/
venv/
Recommended Dependencies (2025-2026)
streamlit>=1.52.0
streamlit[auth] # For authentication (includes Authlib>=1.3.2)
plotly>=5.18.0
altair>=5.2.0
pandas>=2.0.0
orjson # Performance optimization for Plotly
2. Authentication Workflow
Prerequisites
Install authentication dependencies:
pip install "streamlit[auth]"Configure identity provider (Google, Microsoft Entra ID, Okta, Auth0)
Obtain from provider:
- Client ID
- Client secret
- Server metadata URL
secrets.toml Configuration
Single Provider Setup
[auth]
redirect_uri = "http://localhost:8501/oauth2callback" # Use https:// in production
cookie_secret = "your-strong-random-secret-here" # Generate with: python -c "import secrets; print(secrets.token_hex(32))"
client_id = "your-client-id"
client_secret = "your-client-secret"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration" # Google example
Multiple Providers Setup
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "your-strong-random-secret-here"
[auth.google]
client_id = "google-client-id"
client_secret = "google-client-secret"
server_metadata_url = "https://accounts.google.com/.well-known/openid-configuration"
[auth.microsoft]
client_id = "microsoft-client-id"
client_secret = "microsoft-client-secret"
server_metadata_url = "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration"
Authentication Implementation Pattern
import streamlit as st
# Check login status
if not st.user.is_logged_in:
st.title("Welcome to My App")
st.write("Please log in to continue.")
# Single provider
st.login()
# Multiple providers
# col1, col2 = st.columns(2)
# with col1:
# st.login("google")
# with col2:
# st.login("microsoft")
st.stop()
# User is logged in - show main content
st.title(f"Welcome, {st.user.name}!")
st.write(f"Email: {st.user.email}")
# Logout button
if st.button("Logout"):
st.logout()
Accessing User Information
# Available after successful login
st.user.is_logged_in # bool
st.user.name # str
st.user.email # str
# With expose_tokens configured
st.user.tokens.id # ID token (if "id" in expose_tokens)
st.user.tokens.access # Access token (if "access" in expose_tokens)
Security Best Practices
- ALWAYS use HTTPS in production for redirect_uri
- Generate strong cookie_secret:
python -c "import secrets; print(secrets.token_hex(32))" - Never commit secrets.toml - use environment variables or secrets management in deployment
- Identity cookies expire after 30 days - users must re-authenticate
- Authentication not supported for embedded apps
3. Visualization Workflow
Library Selection Guide
| Use Case | Recommended Library | Reason |
|---|---|---|
| Simple charts, KPIs | Native (st.line_chart, st.bar_chart) |
Fastest, zero dependencies |
| Interactive exploration | Plotly | Best interactivity, zoom/pan/hover |
| Statistical visualizations | Altair | Declarative, publication-quality |
| Publication-quality static | Matplotlib/Seaborn | Fine control, PDF export |
| Geospatial mapping | PyDeck | 3D maps, large datasets |
Performance Benchmarks
Rendering Speed (fastest to slowest):
- Native Streamlit charts
- Altair
- Plotly
- Matplotlib
Large Dataset Handling
Altair Limitations and Solutions
import altair as alt
# Altair has a 5,000 row limit by default
# Solution 1: Increase limit (use cautiously)
alt.data_transformers.disable_max_rows()
# Solution 2: Use data URL (recommended for >5000 rows)
# This sends data as a URL reference instead of embedding
alt.data_transformers.enable('vegafusion') # If vegafusion installed
# Solution 3: Downsample data before plotting
def downsample_data(df, max_rows=5000):
if len(df) > max_rows:
return df.sample(n=max_rows, random_state=42)
return df
Plotly Optimization
import plotly.express as px
import streamlit as st
# Install orjson for faster serialization
# pip install orjson
# Use WebGL renderer for large datasets
fig = px.scatter(df, x='x', y='y', render_mode='webgl')
# Reduce data points for smoother interaction
fig.update_traces(marker=dict(size=3))
# Disable expensive animations
fig.update_layout(transition_duration=0)
st.plotly_chart(fig, use_container_width=True, config={'displayModeBar': False})
Chart Configuration Best Practices
import streamlit as st
import plotly.express as px
# Always use container width for responsiveness
st.plotly_chart(fig, use_container_width=True)
# Configure Plotly display options
config = {
'displayModeBar': True,
'displaylogo': False,
'modeBarButtonsToRemove': ['lasso2d', 'select2d'],
'toImageButtonOptions': {
'format': 'png',
'filename': 'chart',
'height': 600,
'width': 800,
'scale': 2
}
}
st.plotly_chart(fig, config=config)
4. Performance Optimization Workflow
Caching Strategy
st.cache_data (for data)
import streamlit as st
import pandas as pd
@st.cache_data(ttl=3600) # Cache for 1 hour
def load_data(file_path: str) -> pd.DataFrame:
"""Load and cache data from file."""
return pd.read_csv(file_path)
@st.cache_data
def expensive_computation(df: pd.DataFrame) -> pd.DataFrame:
"""Cache expensive transformations."""
# Heavy processing here
return processed_df
st.cache_resource (for resources)
import streamlit as st
from sqlalchemy import create_engine
@st.cache_resource
def get_database_connection():
"""Cache database connection (shared across users)."""
return create_engine(st.secrets["database"]["url"])
@st.cache_resource
def load_ml_model():
"""Cache ML model loading."""
import joblib
return joblib.load("model.pkl")
Session State Best Practices
import streamlit as st
# Initialize session state
if "counter" not in st.session_state:
st.session_state.counter = 0
if "data" not in st.session_state:
st.session_state.data = None
# Use session state for user inputs that should persist
def increment():
st.session_state.counter += 1
st.button("Increment", on_click=increment)
st.write(f"Count: {st.session_state.counter}")
Fragment-Based Updates (Partial Reruns)
import streamlit as st
@st.fragment
def chart_section():
"""Only this section reruns when its widgets change."""
chart_type = st.selectbox("Chart Type", ["Line", "Bar", "Scatter"])
# Chart rendering here
st.line_chart(data)
@st.fragment
def filter_section():
"""Independent fragment for filters."""
date_range = st.date_input("Date Range")
category = st.multiselect("Categories", options)
return date_range, category
# Main app
st.title("Dashboard")
filter_section()
chart_section()
Memory Optimization for Large Datasets
import streamlit as st
import pandas as pd
# Use appropriate dtypes
@st.cache_data
def load_optimized_data(file_path: str) -> pd.DataFrame:
df = pd.read_csv(
file_path,
dtype={
'id': 'int32',
'category': 'category',
'value': 'float32'
},
parse_dates=['date']
)
return df
# Stream large files
def process_large_file(file):
chunks = pd.read_csv(file, chunksize=10000)
for chunk in chunks:
# Process each chunk
yield process_chunk(chunk)
5. Secrets Management
Local Development
# .streamlit/secrets.toml (NEVER COMMIT)
[database]
host = "localhost"
port = 5432
username = "user"
password = "password"
[api_keys]
openai = "sk-..."
google_maps = "AIza..."
[auth]
redirect_uri = "http://localhost:8501/oauth2callback"
cookie_secret = "dev-secret"
Accessing Secrets
import streamlit as st
# Dictionary-style access
db_host = st.secrets["database"]["host"]
# Attribute-style access
api_key = st.secrets.api_keys.openai
# Pass entire section as kwargs
import psycopg2
conn = psycopg2.connect(**st.secrets.database)
Production Deployment
Streamlit Community Cloud:
- Use the "Secrets" section in app settings
- Paste secrets.toml content directly
Docker/Kubernetes:
- Use environment variables or mounted secret files
- Map to
.streamlit/secrets.tomlpath
Cloud Providers:
- AWS: Use AWS Secrets Manager
- GCP: Use Secret Manager
- Azure: Use Key Vault
6. Modern Features (v1.42-1.52+)
Theming (v1.44+)
# .streamlit/config.toml
[theme]
primaryColor = "#FF4B4B"
backgroundColor = "#FFFFFF"
secondaryBackgroundColor = "#F0F2F6"
textColor = "#262730"
font = "sans serif"
Runtime Theme Detection (v1.46+)
import streamlit as st
# Detect current theme
theme = st.context.theme
if theme == "dark":
chart_template = "plotly_dark"
else:
chart_template = "plotly_white"
Top Navigation (v1.46+)
import streamlit as st
# Create top navigation
pages = st.navigation([
st.Page("home.py", title="Home", icon="🏠"),
st.Page("dashboard.py", title="Dashboard", icon="📊"),
st.Page("settings.py", title="Settings", icon="⚙️"),
], position="top")
pages.run()
Custom Components v2 (v1.51+)
import streamlit as st
from streamlit.components.v1 import components
# Custom component with bidirectional data flow
my_component = components.declare_component(
"my_component",
path="frontend/build"
)
result = my_component(data=my_data, key="unique_key")
New Widgets (v1.52)
import streamlit as st
from datetime import datetime
# Combined date and time input
dt = st.datetime_input("Select date and time", value=datetime.now())
# Download button with callable
def generate_report():
return create_pdf_report()
st.download_button(
"Download Report",
data=generate_report, # Callable - generates on demand
file_name="report.pdf"
)
# Chat input with audio
message = st.chat_input("Type or speak", accept_audio=True)
7. Common Patterns and Best Practices
Application Structure Pattern
import streamlit as st
def main():
# 1. Page configuration (MUST be first)
st.set_page_config(
page_title="My App",
page_icon="🚀",
layout="wide",
initial_sidebar_state="expanded"
)
# 2. Authentication check
if not st.user.is_logged_in:
show_login_page()
st.stop()
# 3. Initialize session state
init_session_state()
# 4. Sidebar navigation
with st.sidebar:
page = st.selectbox("Navigation", ["Dashboard", "Settings"])
# 5. Main content
if page == "Dashboard":
show_dashboard()
elif page == "Settings":
show_settings()
if __name__ == "__main__":
main()
Error Handling Pattern
import streamlit as st
def safe_operation():
try:
result = risky_operation()
st.success("Operation completed!")
return result
except ConnectionError:
st.error("Connection failed. Please check your network.")
except ValueError as e:
st.warning(f"Invalid input: {e}")
except Exception as e:
st.exception(e) # Shows full traceback in dev
# In production, use st.error() with user-friendly message
Form Pattern (Prevent Unnecessary Reruns)
import streamlit as st
with st.form("my_form"):
name = st.text_input("Name")
email = st.text_input("Email")
submitted = st.form_submit_button("Submit")
if submitted:
# Process form data
save_user(name, email)
st.success("Saved!")
Resources
references/
authentication_guide.md- Detailed OIDC setup for major providersperformance_optimization.md- Advanced caching and optimization techniquesvisualization_best_practices.md- Chart library comparison and usage patternsrelease_notes_summary.md- Key features by version
assets/
app_template.py- Production-ready application templateauth_template.py- Authentication implementation templatedashboard_template.py- Data dashboard templatesecrets_template.toml- secrets.toml template with comments
Quick Reference
| Feature | Version | Key API |
|---|---|---|
| Native Auth | 1.42+ | st.login(), st.logout(), st.user |
| Advanced Theming | 1.44+ | config.toml [theme] |
| Top Navigation | 1.46+ | st.navigation(position="top") |
| Theme Detection | 1.46+ | st.context.theme |
| Custom Components v2 | 1.51+ | components.declare_component() |
| Datetime Input | 1.52+ | st.datetime_input() |
| Callable Downloads | 1.52+ | st.download_button(data=callable) |