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.
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.
4 requirements
4 requirements
1.
Sign up for an Expo account.
2.
npm install -g eas-cli.3.
Create an Expo project and link it to EAS with eas init.
4.
Sign up for a Supabase account.
What you'll learn
- Install and configure Supabase in your React Native app
- Add authentication with the client you create in Step 2
- Set up environments for local development, Production, and Preview
- Manage the integration and troubleshoot common problems
Install and configure Supabase
1
Run the connect command
Run the following command in your project directory:
- eas integrations:supabase:connectWith no project linked, the command creates a new Supabase project. To use a Supabase project you already have, link it instead:
- 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-jsandexpo-sqlite, which stores the auth session, and adds theexpo-sqliteconfig 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_URLandEXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEYto .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.
Never put the database password or a secret key in your app. A secret key bypasses row level security and grants full access to your data.
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.
- eas integrations:supabase:connect --link abcdefghijklmnopqrst- eas integrations:supabase:connect --link https://supabase.com/dashboard/project/abcdefghijklmnopqrstRunning 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:
- eas integrations:supabase:connect --non-interactive --region us-east-1 --overwrite--regionis required when the command creates a project without prompting. It takesamericas,emea,apac, or a specific code such asus-east-1.--overwritereplaces existing environment variables without prompting.--organizationselects a Supabase organization.--jsonimplies--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.
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:
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:
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:
- npx expo startIf 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:
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:
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:
- npx supabase init- npx supabase start- npx supabase statusReplace 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.
The local URL points at your computer. A physical device or an Android Emulator can't reach it, so use your computer's LAN address there instead of127.0.0.1.
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:
- 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 plaintextDon'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:
- eas integrations:supabase:connect --environment previewThe 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:
- eas integrations:supabase:dashboard- eas integrations:supabase:disconnectdashboard 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:
-
Create a project at database.new.
-
Copy the Project URL from API Settings and the Publishable key from API Keys.
-
Install the SDK:
Terminal-npx expo install @supabase/supabase-js expo-sqlite -
Add the
expo-sqliteconfig plugin to your app config. -
Set
EXPO_PUBLIC_SUPABASE_URLandEXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEYin .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.
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.
Don't re-runconnect --environmentin this case. It creates another project, which counts against your plan limit.
Further reading
Combine Supabase Auth and the database in this quickstart guide.
Add Sign in with Apple to your Android and iOS app with Supabase Auth.
Add Sign in with Google to your Android and iOS app with Supabase Auth.
Store your data locally and sync it with Postgres using WatermelonDB.
Implement authentication and file upload in a React Native app.