Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 68 additions & 24 deletions integrations/supabase/guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
</iframe>
</Frame>

Used in conjunction with **Supabase**, PowerSync enables developers to build local-first & offline-first apps that are robust in poor network conditions and that have highly responsive frontends while relying on [Supabase](https://supabase.com/) for their backend. This guide provides instructions for how to configure PowerSync for use with your Supabase project.
This guide shows you how to configure PowerSync with [Supabase](https://supabase.com/). PowerSync syncs your Supabase Postgres database to a local SQLite database on each client, so your app reads and writes locally and stays highly responsive even in poor network conditions.

<Check>
This guide takes 10-15 minutes to complete.
</Check>

<Warning>
Note: We are currently investigating an issue with Supabase logical replication where unused/idle instances have excessive WAL growth, resulting in maxed out disk usage. Supabase seem to have updated the default archive_timeout setting to 2 min, which seems to be part of the issue. The current recommendation for pet/hobby projects is to set a smaller `max_wal_size` and `max_slot_wal_keep_size`:
Expand All @@ -36,19 +40,12 @@
```
</Warning>

<Note>
Before you proceed, this guide assumes that you have already signed up for free accounts with both Supabase and PowerSync Cloud (our cloud-hosted offering). If you haven't signed up for a **PowerSync** (Cloud) account yet, [click here](https://accounts.powersync.com/portal/powersync-signup?s=docs) (and if you haven't signed up for Supabase yet, [click here](https://supabase.com/dashboard/sign-up)).
</Note>

<Note>
For mobile/desktop apps, this guide assumes that you already have **Flutter / React Native / Kotlin / Xcode** set up.

For web apps, this guide assumes that you have [pnpm](https://pnpm.io/installation#using-npm) installed.
</Note>
**Before you start, make sure you have:**

<Check>
This guide takes 10-15 minutes to complete.
</Check>
- A [Supabase](https://supabase.com/dashboard/sign-up) account
- A [PowerSync Cloud](https://accounts.powersync.com/portal/powersync-signup?s=docs) account
- For mobile/desktop apps: Flutter, React Native, Kotlin, or Xcode set up for your platform of choice
- For web apps: [pnpm](https://pnpm.io/installation#using-npm) installed

## Architecture

Expand Down Expand Up @@ -84,6 +81,28 @@

Create a new Supabase project (or use an existing project if you prefer) and follow the below steps.

### Before You Start: Project Creation Settings

When you create a new Supabase project, two settings on the project creation screen affect how the demo app behaves. Reviewing them now saves debugging later.

**Automatically expose new tables and functions**

This controls whether tables you create in the `public` schema are reachable via the Supabase Data API. The Data API is the auto-generated REST and GraphQL layer that the Supabase client libraries (such as `supabase-js`) call. In this integration, PowerSync handles the download path by replicating data from Postgres to client SQLite databases, but client writes still go back to Postgres through the Data API using `supabase-js`. If the tables are not exposed, the demo app cannot write changes back to your database. Supabase is rolling out a new default:

- **From April 28, 2026:** the setting is available as an opt-in. New projects can choose to require explicit `grant` statements.
- **From May 30, 2026:** the new default for all new projects is that tables in `public` are not exposed to the Data API on creation.
- **From October 30, 2026:** the new default is enforced on all existing projects.

The schema SQL in this guide includes explicit `grant` statements, so the demo app works under either default. See [this](https://github.com/orgs/supabase/discussions/45329) for more details.

**Enable automatic RLS**

This adds an event trigger that automatically enables Row Level Security on every new table created in the `public` schema. Enabling it is recommended because RLS is the authoritative security layer for any writes that reach Postgres through the Data API. If you leave it disabled, you can still enable RLS per table as the schema SQL in this guide does.

<Warning>
Without RLS, any user with the `anon` or `authenticated` key can read and write every row in the table, regardless of who created it.
</Warning>

### Create the Demo Database Schema

To set up the Postgres database for our _To-Do List_ demo app, we will create two new tables: `lists` and `todos`. The demo app will have access to these tables even while offline.
Expand Down Expand Up @@ -116,8 +135,38 @@
constraint todos_completed_by_fkey foreign key (completed_by) references auth.users (id) on delete set null,
constraint todos_list_id_fkey foreign key (list_id) references lists (id) on delete cascade
) tablespace pg_default;

-- Grant Data API access so supabase-js can read and write these tables.
grant select, insert, update, delete on public.lists to authenticated;
grant select, insert, update, delete on public.todos to authenticated;
grant select, insert, update, delete on public.lists to service_role;
grant select, insert, update, delete on public.todos to service_role;

-- Enable Row Level Security and add policies so each user can only access their own data.
alter table public.lists enable row level security;
alter table public.todos enable row level security;

create policy "owned lists" on public.lists
for all to authenticated
using (auth.uid() = owner_id);

create policy "todos in owned lists" on public.todos
for all to authenticated
using (
auth.uid() in (
select lists.owner_id from public.lists where lists.id = todos.list_id
)
);
```

<Info>
**Grants and RLS do different jobs, and you need both.** Grants control which Postgres roles can touch a table through the Data API. Without a grant, `supabase-js` returns a `42501: permission denied for table` error before RLS is ever evaluated. RLS runs after the grant succeeds and filters which rows that role can read or modify. Skipping RLS means any user with the `anon` or `authenticated` key can read and write every row.

This guide grants `authenticated` because the demo app requires sign-in. The `anon` role is intentionally left ungranted. If you wanted to expose a table for public read-only access (for example, a shared catalog visible to signed-out users), you would add `grant select on public.your_table to anon` along with an RLS policy scoping which rows `anon` can read.

Check warning on line 165 in integrations/supabase/guide.mdx

View check run for this annotation

Mintlify / Mintlify Validation (powersync) - vale-spellcheck

integrations/supabase/guide.mdx#L165

Did you really mean 'ungranted'?
</Info>

PowerSync's Sync Streams (configured later in this guide) control which rows get downloaded to each client and generally mirror your RLS setup. For more on how these work together, see [RLS and Sync Streams](/integrations/supabase/rls-and-sync-streams).

### Create a PowerSync Database User

PowerSync uses the Postgres [Write Ahead Log (WAL)](https://www.postgresql.org/docs/current/wal-intro.html) to replicate data changes in order to keep PowerSync SDK clients up to date.
Expand Down Expand Up @@ -176,8 +225,8 @@
</Tab>
</Tabs>

2. Click **"Validate"** and ensure there are no errors. This validates your sync config against your Postgres database.
3. Click **"Deploy"** to deploy your sync config.
3. Click **"Validate"** and ensure there are no errors. This validates your Sync Config against your Postgres database.
4. Click **"Deploy"** to deploy your Sync Config.

<Tip>
- For additional information on PowerSync's Sync Streams, refer to the [Sync Streams](/sync/streams/overview) documentation.
Expand Down Expand Up @@ -266,8 +315,11 @@

</CodeGroup>

<Note>
Supabase has replaced the legacy anon key with **publishable keys** (prefixed `sb_publishable_…`) by default on new projects. The demos still read environment variables named `SUPABASE_ANON_KEY` / `supabaseAnonKey`, but the value is interchangeable: paste your publishable key into the same variable. Existing projects that still use legacy JWT keys can continue to use the anon key directly.
</Note>

1. In the relevant config file, replace the values for `supabaseUrl` (from the [Project URL](https://supabase.com/dashboard/project/_/settings/api) section in the Supabase dashboard) and `supabaseAnonKey` (from the [API Keys](https://supabase.com/dashboard/project/_/settings/api-keys) section in the Supabase dashboard)
1. In the relevant config file, replace the values for `supabaseUrl` (from the [Project URL](https://supabase.com/dashboard/project/_/settings/api) section in the Supabase dashboard) and `supabaseAnonKey` (from the [API Keys](https://supabase.com/dashboard/project/_/settings/api-keys) section in the Supabase dashboard).
2. For the value of `powersyncUrl`, click **Connect** in the top bar of the [PowerSync Dashboard](https://dashboard.powersync.com/) and copy the instance URL from the dialog.

#### Run the App
Expand All @@ -282,10 +334,6 @@
```

```bash React Native
# In the repo root directory:
pnpm install
pnpm build:packages

# In `demos/react-native-supabase-todolist`:
# Run on iOS
pnpm ios
Expand All @@ -294,10 +342,6 @@
```

```bash JavaScript Web
# In the repo root directory:
pnpm install
pnpm build:packages

# In `demos/react-supabase-todolist`:
pnpm dev
```
Expand Down
33 changes: 14 additions & 19 deletions integrations/supabase/rls-and-sync-streams.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: "How PowerSync Sync Streams work alongside Supabase Row Level Secur
PowerSync's [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync/rules/overview)) and Supabase's support for [Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security) can be used in conjunction. Here are some high level similarities and differences:

* RLS should be used as the authoritative set of security rules applied to your users' CRUD operations that reach Postgres.
* Sync Streams (or legacy Sync Rules) are only applied for data that is to be downloaded to clients — they do not apply to uploaded data.
* Sync Streams (or legacy Sync Rules) are only applied for data that is to be downloaded to clients. They do not apply to uploaded data.
* Sync Streams / Sync Rules can typically be considered to be complementary to RLS, and will generally mirror your RLS setup.

<Note>
Expand All @@ -15,31 +15,26 @@ PowerSync's [Sync Streams](/sync/streams/overview) (or legacy [Sync Rules](/sync

### Example

Continuing with the schema set up during the guide, below are the RLS policies for the to-do list app:
The [Supabase + PowerSync guide](/integrations/supabase/guide#create-the-demo-database-schema) sets up RLS policies for the to-do list demo app:

```sql
alter table public.lists
enable row level security;

alter table public.todos
enable row level security;

create policy "owned lists" on public.lists for ALL using (
auth.uid() = owner_id
);

create policy "todos in owned lists" on public.todos for ALL using (
auth.uid() IN (
SELECT lists.owner_id FROM lists WHERE (lists.id = todos.list_id)
)
);

create policy "owned lists" on public.lists
for all to authenticated
using (auth.uid() = owner_id);

create policy "todos in owned lists" on public.todos
for all to authenticated
using (
auth.uid() in (
select lists.owner_id from public.lists where lists.id = todos.list_id
)
);
```

`auth.uid()` in a Supabase RLS policy maps to:
- `auth.user_id()` in [Sync Streams](/sync/streams/overview)
- `request.user_id()` (previously `token_parameters.user_id`) in legacy [Sync Rules](/sync/rules/overview)

If you compare these to your sync config, you'll see the access patterns are quite similar.
If you compare these policies to the Sync Config in the guide, you'll see the access patterns mirror each other.

If you have any questions, join us on [our community Discord](https://discord.gg/powersync) where our team is always available to help.