Real apps talk to databases and APIs, and credentials must never end up in the code or the repository.
Secrets go in .streamlit/secrets.toml (in the project or ~/.streamlit/), written in TOML:
api_key = "sk-..."
[connections.warehouse]
url = "postgresql://user:pass@host:5432/db"Read them with st.secrets["api_key"] or st.secrets.connections.warehouse.url. Root-level keys are also exposed as environment variables. Add secrets.toml to .gitignore; on Streamlit Community Cloud and other hosts you paste secrets into the platform's settings instead.
`st.connection` creates a cached, configured connection from those secrets:
conn = st.connection("warehouse", type="sql")
df = conn.query("select * from sales where region = :r", params={"r": region}, ttl="10m")type="sql"uses SQLAlchemy and needs a driver for your database;type="snowflake"is also built in, and third-party packages provide others.queryresults are cached withst.cache_dataunder the hood;ttlcontrols freshness.- The connection object itself is cached as a resource, created once per app process.
- Use bound parameters (
:r) instead of f-strings to avoid SQL injection.
For services without a connection class, read the key from st.secrets and create your client inside an @st.cache_resource function.
Secrets are server-side only: they never reach the browser unless you display them. Be careful not to st.write(st.secrets) while debugging a deployed app.
Example
# .streamlit/secrets.toml (never commit this file):
# api_key = "demo-key"
# [connections.shop]
# url = "sqlite:///shop.db"
import streamlit as st
conn = st.connection("shop", type="sql") # reads [connections.shop]
region = st.selectbox("Region", ["EU", "US"])
df = conn.query("select :r as region, 42 as amount",
params={"r": region}, ttl="10m") # cached per params
st.dataframe(df)
key = st.secrets["api_key"] # use it, never display it
st.write("API key loaded:", bool(key))Remember
- Keep credentials in
.streamlit/secrets.toml(git-ignored) and read viast.secrets st.connection(name, type='sql')builds a cached connection from secretsconn.query(..., params=..., ttl=...)caches results and avoids SQL injection
Watch out for
- Committing secrets.toml to Git, or formatting user input directly into SQL strings.