This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

Using Supabase

Edit page

Add a Postgres database and user authentication to your React Native app with Supabase.

Android
iOS

Supabase is a Backend-as-a-Service (BaaS) app development platform built on Postgres. It generates a REST API from your database and uses row level security to protect the data, so your React Native app can query that API directly, with no server in between.

The EAS CLI integration automates the standard setup: authorizing your Supabase account, creating or linking a project, installing the SDK, and writing your environment variables. You can also set it up manually and use the rest of this guide unchanged.

Prerequisites

4 requirements

1.

Expo account

Sign up for an Expo account.

2.

EAS CLI
Install EAS CLI globally with npm install -g eas-cli.

3.

Expo project linked to EAS

Create an Expo project and link it to EAS with eas init.

4.

Supabase account

Sign up for a Supabase account.

What you'll learn

Install and configure Supabase

1

Run the connect command

Run the following command in your project directory:

Terminal
eas integrations:supabase:connect

With no project linked, the command creates a new Supabase project. To use a Supabase project you already have, link it instead:

Terminal
eas integrations:supabase:connect --link <project-ref-or-url>

This command:

  • Opens your browser to authorize Supabase, then continues after you approve.
  • Asks which Supabase organization to use, if your account has more than one.
  • Asks for a region from Americas, Europe/Middle East/Africa, and Asia Pacific, then creates the project and waits until it's ready. The region sets your data residency and can't be changed after the project is created.
  • Installs @supabase/supabase-js and expo-sqlite, which stores the auth session, and adds the expo-sqlite config plugin to your app config. If you use a dynamic app config, the command prints the plugin entry for you to add instead.
  • Writes EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY to .env.local and to your EAS environment variables across the Production, Preview, and Development environments.

Both values are meant to be public, so your app can ship them. Anyone who has them can query your database, so row level security is what keeps your data private. Enable it and add policies on every table your app reads or writes.

Re-running connect is safe: it reuses your existing connection and Supabase project, and prompts before overwriting environment variables.

Finding your project reference ID

--link accepts a reference ID, a dashboard URL, or a project API URL. Supabase shows the reference ID under Project Settings > General. A project name doesn't work.

Terminal
eas integrations:supabase:connect --link abcdefghijklmnopqrst

eas integrations:supabase:connect --link https://supabase.com/dashboard/project/abcdefghijklmnopqrst
Running in CI or non-interactively

EAS Build and EAS Update read the variables from the environment they run in, so run connect in CI only when CI creates or links the project:

Terminal
eas integrations:supabase:connect --non-interactive --region us-east-1 --overwrite
  • --region is required when the command creates a project without prompting. It takes americas, emea, apac, or a specific code such as us-east-1.
  • --overwrite replaces existing environment variables without prompting.
  • --organization selects a Supabase organization.
  • --json implies --non-interactive.

Authorizing a Supabase account needs a browser, so run connect interactively at least once first.

2

Create the Supabase client

Create a helper file that initializes the client from the environment variables connect writes. Paths here follow the default Expo template, where @/ maps to src. Check the paths field in tsconfig.json and put the file where your alias resolves, or use a relative import if your project has no alias.

src/lib/supabase.ts
import 'expo-sqlite/localStorage/install'; import { createClient } from '@supabase/supabase-js'; const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL!; const supabasePublishableKey = process.env.EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY!; export const supabase = createClient(supabaseUrl, supabasePublishableKey, { auth: { storage: localStorage, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, }, });

expo-sqlite/localStorage/install provides the localStorage that Supabase uses to persist sessions on the device, which keeps users signed in across app launches. detectSessionInUrl is false because Android and iOS have no URL to read a session from. Supabase's own quickstart also imports react-native-url-polyfill/auto, which Expo projects don't need, because Expo installs a URL global already.

3

Create a table

Run eas integrations:supabase:dashboard to open your linked project, or open it from the Supabase dashboard. Select SQL Editor, then run:

Supabase SQL Editor
create table public.todos ( id bigint generated always as identity primary key, title text not null ); alter table public.todos enable row level security; create policy "Anyone can read todos" on public.todos for select using (true); grant select on public.todos to anon, authenticated; insert into public.todos (title) values ('Hello from Supabase');

Requests use the anon role when signed out and authenticated after sign-in, and the policy decides which rows those roles can read. Without a policy, a select returns an empty array and no error. To let the app write, add an insert policy.

The grant is a safeguard rather than a requirement. Hosted projects already grant select, insert, update, and delete on new tables in public to both roles. However, Supabase is making those grants opt-in, and a project with them revoked fails with permission denied for table todos. Granting a privilege the table already has changes nothing.

4

Verify the configuration

Replace the contents of your first screen with a query against the table:

src/app/index.tsx
import { useEffect, useState } from 'react'; import { Text, View } from 'react-native'; import { supabase } from '@/lib/supabase'; export default function Index() { const [titles, setTitles] = useState<string[]>([]); useEffect(() => { supabase .from('todos') .select() .then(({ data, error }) => { if (error) { setTitles([error.message]); return; } setTitles(data.map(todo => todo.title)); }); }, []); return ( <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> {titles.map(title => ( <Text key={title}>{title}</Text> ))} </View> ); }

Start your app:

Terminal
npx expo start

If the screen shows Hello from Supabase, your client works. An empty screen means no policy allows the read. An error message means something else, so see Troubleshooting.

You can test everything in this guide in Expo Go. You need a development build once you pass options to the expo-sqlite plugin or add other native libraries.

For the concepts behind these steps, see the Supabase database overview:

Supabase database overview

Tables, row level security policies, and realtime updates.

Add authentication

The client from Step 2 already stores sessions, so email and password sign-in needs no extra configuration. New Supabase projects confirm email addresses by default. Your first signUp returns data.user with data.session set to null until the address is confirmed or you turn off Confirm email in your project's email provider settings.

autoRefreshToken runs its refresh loop continuously on Android and iOS, so Supabase's startAutoRefresh reference recommends tying the loop to app state. Add this to your client file:

src/lib/supabase.ts
import { AppState } from 'react-native'; AppState.addEventListener('change', state => { if (state === 'active') { supabase.auth.startAutoRefresh(); } else { supabase.auth.stopAutoRefresh(); } });

OAuth providers and magic links need a deep link back into your app, covered in Supabase's mobile deep linking guide.

Set up environments

connect stores the values as EAS environment variables, so each build or update reads them from the environment it runs in.

A Supabase project holds one environment's data, so separate environments mean separate projects. Check your Supabase plan limits before creating a second project.

Development runs locally with the Supabase CLI. Start Docker, then run:

Terminal
npx supabase init
npx supabase start
npx supabase status

Replace the hosted values in .env.local with the API URL and publishable key that supabase status prints. Variables you export in your shell take precedence over .env.local, so edit the file instead of exporting. The local database starts empty, so run your table SQL against it too. Re-running connect writes the hosted values back.

Pointing the Development environment at a local stack

connect writes each value as one variable covering Production, Preview, and Development. eas env:pull --environment development asks before replacing .env.local, then rewrites the whole file from that environment, so it drops the local values you set.

To point Development at a local stack instead, replace that one variable with two. eas env:set reuses a variable of the same name whose environments overlap the target, so a Development-only set would move Production and Preview to the local URL too:

Terminal
eas env:delete --variable-name EXPO_PUBLIC_SUPABASE_URL

eas env:set --name EXPO_PUBLIC_SUPABASE_URL --value <hosted-url> --environment production --environment preview --visibility plaintext

eas env:set --name EXPO_PUBLIC_SUPABASE_URL --value http://127.0.0.1:54321 --environment development --visibility plaintext

Don't do this if any build profile in eas.json sets "environment": "development". A cloud build then embeds a URL that no device can reach.

Production uses the project that connect set up.

Preview has no project of its own by default. To give Preview, or any other EAS environment, its own hosted project, re-run connect with --environment. This creates a second project, which counts toward your plan's active-project limit:

Terminal
eas integrations:supabase:connect --environment preview

The new project's URL and key go to the named environments only, and your other environments keep pointing at the first project. If the target environments already hold EXPO_PUBLIC_SUPABASE_* values, the command asks to confirm before replacing them. Unlike connect without --environment, this doesn't install the SDK or touch .env.local. You can't combine --environment with --link, --reauth, or --organization.

To point an EAS environment at a Supabase project you already have, set the two variables yourself with eas env:set rather than using --environment. Replace the shared variable with two first, as shown above, so your other environments keep their current project.

Manage the integration

Two commands manage the integration afterward:

Terminal
eas integrations:supabase:dashboard

eas integrations:supabase:disconnect

dashboard opens your linked Supabase project. disconnect removes the Expo-side link only. Your Supabase project, its data, and your environment variables all stay unchanged.

After you disconnect, connect no longer sees a linked project, so it creates a new one. To point the app back at the same project, run connect --link with its reference ID.

Manual setup

connect is a shortcut for the standard Supabase setup. To do it manually:

  1. Create a project at database.new.

  2. Copy the Project URL from API Settings and the Publishable key from API Keys.

  3. Install the SDK:

    Terminal
    npx expo install @supabase/supabase-js expo-sqlite
  4. Add the expo-sqlite config plugin to your app config.

  5. Set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY in .env.local, then create the client file from Step 2.

Troubleshooting

Active project limit reached

The Free plan entitles each user to two active projects, and an organization pools the entitlement of every owner and administrator in it. Paused projects don't count. Link a project you already have with --link, pause or delete one in the Supabase dashboard, upgrade the organization, or see Supabase's billing FAQ for how the entitlement is shared. --environment can't be combined with --link, so on that path free a slot or set EXPO_PUBLIC_SUPABASE_URL and EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY on the target environments yourself.

Project reference ID doesn't work

The project must belong to the Supabase organization you connected. See Finding your project reference ID for the accepted formats.

Environment variables don't update

After connect writes them, reload the app so it picks up the new values. If the app still reads the old value, stop the development server and run npx expo start again.

Table doesn't exist right after you create it

Supabase caches your database schema, so a query right after you create a table can fail with Could not find the table 'public.todos' in the schema cache. Re-run it; the cache refreshes on its own.

Permission denied for a table

Add the grant your query needs for both roles, such as grant select on public.todos to anon, authenticated. A policy alone isn't enough. Unlike a missing policy, which returns an empty array, this fails with error.code 42501.

Supabase authorization stopped working

If you revoked Expo's access in Supabase, run eas integrations:supabase:connect --reauth. This clears the stored connection and project link, reopens the browser, then asks whether to link a project or create one, so have your reference ID ready. Your Supabase projects aren't affected. --reauth needs a browser, so it fails in non-interactive mode.

Extra project created without environment variables

If connect --environment creates a project and then fails to write the environment variables, the command prints the project URL and publishable key. Save those values with eas env:set.

Further reading

Build a user management app

Combine Supabase Auth and the database in this quickstart guide.

Sign in with Apple

Add Sign in with Apple to your Android and iOS app with Supabase Auth.

Sign in with Google

Add Sign in with Google to your Android and iOS app with Supabase Auth.

Offline-first apps with WatermelonDB

Store your data locally and sync it with Postgres using WatermelonDB.

File upload with Supabase Storage

Implement authentication and file upload in a React Native app.