# Set up Supabase

> Create PostgreSQL and private S3-compatible attachment storage without exposing Supabase keys to the browser.


Supabase is a convenient foundation because one project can provide both PostgreSQL and S3-compatible storage. Coop does not use Supabase Auth or the browser Data API.

## 1. Create the project

Create a Supabase project in the region closest to the API host. Save the database password in your secret manager. Disable **Enable Data API** and **Automatically expose new tables** because Coop connects through PostgreSQL and does not use Supabase's browser Data API. Wait until the project reports healthy before copying connection strings.

<figure class="doc-figure">
  <img src="/screenshots/supabase-create-project.webp" alt="Supabase new project form showing database, region, and security settings" width="1340" height="1100" loading="lazy">
  <figcaption>Enter a project name, generate a strong database password, and choose the region closest to the API. The Data API options shown here are on by default; turn both off for Coop.</figcaption>
</figure>

## 2. Copy two database URLs

Open **Connect** in the Supabase dashboard. Copy the exact host and username shown there. Do not construct the pooler hostname from the region.

Use a transaction-pooler URL for the running API when your host has many transient connections:

```text
DATABASE_URL=postgresql://postgres.<project-ref>:<password>@<pooler-host>:6543/postgres?sslmode=require&default_query_exec_mode=simple_protocol
```

Use the session-pooler URL, or a reachable direct URL, for migrations:

```text
MIGRATION_DATABASE_URL=postgresql://postgres.<project-ref>:<password>@<pooler-host>:5432/postgres?sslmode=require
```

The separation matters. Transaction mode does not support prepared statements, and Coop migrations use a session-scoped advisory lock. A migration through the transaction pooler can lose that lock.

If the password contains `&`, `#`, `?`, a space, or another reserved URL character, percent-encode it before placing it in either URL.

## 3. Create private attachment storage

Open **Storage** and create a bucket named `coop-media`. Keep it private. The API creates object keys, but it does not create the bucket.

Open the S3 configuration page and generate server-side S3 access keys. These credentials bypass Storage RLS and have broad bucket access, so store them only on the API service.

Set:

```text
MEDIA_S3_ENDPOINT=https://<project-ref>.storage.supabase.co/storage/v1/s3
MEDIA_S3_BUCKET=coop-media
MEDIA_S3_REGION=<region shown by Supabase>
MEDIA_S3_ACCESS_KEY_ID=<generated access key>
MEDIA_S3_SECRET_ACCESS_KEY=<generated secret key>
MEDIA_S3_ADDRESSING_STYLE=path
```

Do not use the database password, browser publishable key, or Supabase service-role key as S3 credentials.

## 4. Record backup ownership

Confirm which plan provides the retention your company needs. Record who can restore the database and who can recover the Coop encryption key. Supabase database backups do not replace object-storage backup or encryption-key recovery.

Official references: [Supabase database connections](https://supabase.com/docs/guides/database/connecting-to-postgres) and [S3 access keys](https://supabase.com/docs/guides/storage/s3/authentication).

