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

# Connect Supabase to your AI agent

> Point Cursor or Claude Code at a Supabase database through Midplane — which of the four connection strings to use, and the pooler username that trips everyone up.

Supabase gives you four connection strings for the same database, and they are not interchangeable. Two are IPv6-only unless you've bought an add-on, one drops session features, and one needs a username you won't guess. This page picks the right one and hands you a least-privilege role to go with it.

Once connected, your agent reaches Supabase only through Midplane: every statement is parsed, checked against [policy](/docs/policies/overview), and written to the [audit trail](/docs/concepts/audit-trail) before it runs.

## Use the session pooler

In the Supabase dashboard, open **Connect** and choose **Session pooler**. Copy the string it gives you rather than assembling one — the pooler host carries a region and an index that vary by project. It looks like this:

```
postgres://postgres.abcdefghijklmnop:PASSWORD@aws-0-eu-central-1.pooler.supabase.com:5432/postgres
```

It's the right default for Midplane because it's the only option that's IPv4 on every plan, and it supports the full Postgres feature set. Here's how the four compare:

| Connection string               | Host and port                           | Reachable from Cloud?                   |
| ------------------------------- | --------------------------------------- | --------------------------------------- |
| **Session pooler** *(use this)* | `aws-<region>.pooler.supabase.com:5432` | Yes — IPv4 on every plan                |
| Transaction pooler              | `aws-<region>.pooler.supabase.com:6543` | Yes, but see [below](#transaction-mode) |
| Direct connection               | `db.<project-ref>.supabase.co:5432`     | Only with the IPv4 add-on               |
| Dedicated pooler                | `db.<project-ref>.supabase.co:6543`     | Only with the IPv4 add-on, paid plans   |

<Note>
  The direct and dedicated endpoints publish an IPv6 (`AAAA`) record by default. Supabase's IPv4 add-on **swaps** that record for an IPv4 (`A`) one rather than serving both, so enabling it changes which networks can reach you. The shared pooler avoids the question entirely.
</Note>

### Transaction mode

Port `6543` works, with one caveat worth knowing: transaction mode reuses a backend connection per statement, so session state doesn't persist and SQL-level `PREPARE` / `EXECUTE` aren't supported. Your agent sends independent statements, so this rarely bites — but session mode costs nothing extra and has no such edge, so prefer `5432`.

## The username includes your project ref

This is the one that wastes an afternoon. On the shared pooler, Supavisor routes by tenant, and the tenant comes from the **username** — the role name and the project ref, joined by a dot:

```
<role>.<project-ref>
```

So the `postgres` role becomes `postgres.abcdefghijklmnop`, and the `midplane` role you're about to create becomes `midplane.abcdefghijklmnop`. Get it wrong and Supabase rejects the connection with:

```
FATAL: Tenant or user not found
```

That error means the username, not the password. On the **direct** connection the plain role name is correct — which is exactly why a string that works in `psql` against `db.<ref>.supabase.co` fails when you move it to the pooler.

## Create a least-privilege role

Don't hand Midplane the `postgres` role. In the Supabase SQL editor:

```sql theme={null}
CREATE ROLE midplane LOGIN PASSWORD 'a-strong-password';
GRANT CONNECT ON DATABASE postgres TO midplane;
GRANT USAGE ON SCHEMA public TO midplane;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO midplane;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO midplane;
```

Then assemble the connection string with the pooler host and the dotted username:

```
postgres://midplane.<project-ref>:a-strong-password@aws-<region>.pooler.supabase.com:5432/postgres?sslmode=require
```

Paste that into **Connect Postgres** in the [dashboard](https://app.midplane.ai), leave the default access level at `read`, and connect your agent. See [connect your agent](/docs/agents/overview) for the client config.

## Row Level Security still applies

A custom role is subject to [RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) like any other non-superuser. On a table with RLS enabled and no policy matching your role, the agent's `SELECT` succeeds and returns **zero rows** — which reads as an empty table rather than as a permission problem.

You have two honest options:

* **Leave RLS in force** and write a policy granting `midplane` the reads it needs. RLS stays a real boundary for the agent, and Midplane layers on top of it.
* **Grant `BYPASSRLS`** so the agent sees the underlying tables. Be deliberate about this: RLS is then no longer a boundary for the agent, and [table access](/docs/concepts/table-access) plus [column masking](/docs/concepts/masking) become the boundary instead.

Neither is wrong — but pick one on purpose. An agent silently reading empty tables is the failure mode worth avoiding.

## Mask what the agent shouldn't see

A Supabase project usually holds `auth.users` alongside your application tables, so it's worth a pass through **Exposure** in the project workspace: scan for likely PII and [mask](/docs/concepts/masking) the columns your agent has no reason to read in the clear. Masking happens before results leave Postgres, so the raw values never reach the model or the audit row.

## Using this alongside the Supabase MCP server

They do different jobs and can coexist. Supabase's own [MCP server](https://supabase.com/docs/guides/ai-tools/mcp) manages the *project* — branches, migrations, edge functions, config — and Supabase recommends scoping it to a development project. Midplane fronts the *database* with a SQL parser and an audit trail, which is what you want pointed at data you care about.

A common split: the Supabase MCP server on a dev project for schema work, Midplane on the database that holds real rows.

## Next steps

<Columns cols={2}>
  <Card title="Write a policy" icon="shield-check" href="/docs/policies/overview" horizontal>
    Grant the reads and writes your agent actually needs.
  </Card>

  <Card title="Mask sensitive columns" icon="eye-off" href="/docs/concepts/masking" horizontal>
    Redact values before they leave the database.
  </Card>
</Columns>
