Secrets and data connections

Streamlit · Practical · 12 min

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.
  • query results are cached with st.cache_data under the hood; ttl controls 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 via st.secrets
  • st.connection(name, type='sql') builds a cached connection from secrets
  • conn.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.

Check yourself

  1. How does `conn.query(sql, ttl='10m')` avoid hitting the database on every rerun?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.streamlit.io/develop/concepts/connections/secrets-management