> ## Documentation Index
> Fetch the complete documentation index at: https://dreamlit.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Technical deep dive

> How Dreamlit's database connection works.

Dreamlit connects to your database the same way analytics and ETL tools you probably already run do, such as Airbyte, Metabase, or Fivetran: a read-only user that reads your tables and follows the change feed. Nothing is installed in your database, and nothing is ever written to it. This page covers exactly what that connection can and can't do, and what it costs your database.

## Connecting, at a glance

You can connect any PostgreSQL database, including Supabase, Neon, RDS, and Cloud SQL. Dreamlit uses the connection for two things: building and diagnosing your user funnel, and hooking into key events in real time to kick off email workflows, with no code for your engineering team to instrument.

It is best practice to create a separate database user for each service that
connects to your database, following security principles of least privilege
and isolation. That way you can see exactly what each one is allowed to do,
and revoke any of them independently.

To create one for Dreamlit, you can run the following SQL, replacing `<SECRET_PASSWORD>` with a [strong, unique password](https://1password.com/password-generator):

```sql theme={null}
CREATE ROLE dreamlit_readonly WITH LOGIN REPLICATION PASSWORD '<SECRET_PASSWORD>';
GRANT pg_read_all_data TO dreamlit_readonly;
```

You'll notice the role has two main permissions attached to it: reading data, and replication.

## Read-only access

The `dreamlit_readonly` user has read access and nothing else. It can't insert, update, delete, or alter anything, and it can't create databases or other users. No changes are ever made to your data.

Read access is what lets Dreamlit build your funnel and render email previews with real rows. By default it's granted through `pg_read_all_data`, PostgreSQL's built-in read-only role, which covers tables you add later without touching the grant again.

If you'd rather limit it further, grant `SELECT` on specific tables instead:

```sql theme={null}
CREATE ROLE dreamlit_readonly WITH LOGIN REPLICATION PASSWORD '<SECRET_PASSWORD>';
GRANT USAGE ON SCHEMA public TO dreamlit_readonly;
GRANT SELECT ON public.users, public.projects TO dreamlit_readonly;
```

## Replication

Replication access is what lets a workflow start the moment something happens, instead of on a polling schedule. It works through PostgreSQL logical replication using [wal2json](https://github.com/eulerto/wal2json), the same mechanism your database uses for its own replicas.

**By default, nothing is replicated.** Connecting the user creates no replication slot and follows no tables. Only when a workflow goes live does Dreamlit subscribe to that workflow's trigger table. Tables with no live workflow are never streamed.

**No data is retained.** The change feed is not copied anywhere. Each change is evaluated against the live workflows and discarded. The only thing that persists is the state a running workflow needs to finish, such as which user it's emailing and which step it's on, and that lives only as long as the run.

**It's robust to outages.** If the connection drops, or Dreamlit is briefly unavailable, your database queues the changes in the slot until we reconnect, then Dreamlit picks up exactly where it left off. Nothing is missed, and nobody gets the same email twice. The queue is bounded by your `max_slot_wal_keep_size` setting, so it can't grow without limit.

To see what Dreamlit is currently subscribed to, run this on your database:

```sql theme={null}
SELECT slot_name, plugin, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots
WHERE plugin = 'wal2json';
```

No rows means nothing is being replicated. One row per connection means a workflow is live; the tables it follows are listed on that connection in your Dreamlit dashboard, and the `active` column shows whether Dreamlit is connected right now.

## Performance

Following the change feed is light work for your database, much lighter than polling your tables. Your database decodes only the tables Dreamlit is subscribed to, and the read-only queries for funnel analysis run in small batches in the background.

If you'd rather keep Dreamlit off your primary entirely, point it at a read replica. Logical replication from a replica requires PostgreSQL 16 or newer; on older versions, the connection needs the primary for the change feed and can use a replica for everything else.
