To connect Supabase to React Native, install @supabase/supabase-js and @react-native-async-storage/async-storage, then create a Supabase client configured with AsyncStorage for session persistence. The JavaScript client works in React Native with one key difference: you must provide a custom storage adapter since localStorage is not available. Auth, database queries, and realtime subscriptions use the same API as web React apps.
Integrating Supabase into a React Native Application
This tutorial walks you through connecting a React Native app to Supabase. You will install the JavaScript client, configure it with AsyncStorage for mobile session persistence, implement authentication flows, and perform database operations. The Supabase JS client works in React Native with minimal configuration changes from web React, making it easy for React developers to build mobile apps.
Prerequisites
- A React Native or Expo project set up and running
- A Supabase project with your URL and anon key
- Node.js 18+ installed
- Basic React Native and TypeScript knowledge
Step-by-step guide
Install Supabase and AsyncStorage packages
Install Supabase and AsyncStorage packages
Install the Supabase JavaScript client and the AsyncStorage package for React Native. AsyncStorage is required because React Native does not have localStorage. The Supabase client uses it to persist the auth session between app launches. For Expo projects, use @react-native-async-storage/async-storage which is included in the Expo SDK.
1# For Expo projects2npx expo install @supabase/supabase-js @react-native-async-storage/async-storage34# For bare React Native projects5npm install @supabase/supabase-js @react-native-async-storage/async-storage6npx pod-install # iOS onlyExpected result: Both packages are installed and ready to import in your React Native code.
Create the Supabase client with AsyncStorage
Create the Supabase client with AsyncStorage
Create a Supabase client module that configures AsyncStorage as the storage backend. This is the only difference from a web React setup — the rest of the API is identical. The auth configuration tells the Supabase client to use AsyncStorage instead of localStorage for persisting tokens. Set autoRefreshToken to true so the client automatically refreshes expired JWTs.
1// src/lib/supabase.ts2import AsyncStorage from '@react-native-async-storage/async-storage'3import { createClient } from '@supabase/supabase-js'45const supabaseUrl = 'https://your-project-ref.supabase.co'6const supabaseAnonKey = 'your-anon-key-here'78export const supabase = createClient(supabaseUrl, supabaseAnonKey, {9 auth: {10 storage: AsyncStorage,11 autoRefreshToken: true,12 persistSession: true,13 detectSessionInUrl: false,14 },15})Expected result: A configured Supabase client that persists auth sessions in AsyncStorage across app restarts.
Implement email/password authentication
Implement email/password authentication
Build sign up, sign in, and sign out functions using the Supabase auth API. These work exactly the same as in web React. The client automatically stores the session in AsyncStorage after a successful sign in. When the app restarts, it reads the stored session and automatically refreshes the token if needed.
1import { supabase } from '../lib/supabase'23export async function signUp(email: string, password: string) {4 const { data, error } = await supabase.auth.signUp({5 email,6 password,7 })8 if (error) throw error9 return data10}1112export async function signIn(email: string, password: string) {13 const { data, error } = await supabase.auth.signInWithPassword({14 email,15 password,16 })17 if (error) throw error18 return data19}2021export async function signOut() {22 const { error } = await supabase.auth.signOut()23 if (error) throw error24}Expected result: Users can sign up, sign in, and sign out. The session persists in AsyncStorage between app restarts.
Create an auth state hook for React Native
Create an auth state hook for React Native
Build a custom hook that tracks the current user and loading state. It checks for an existing session on mount and subscribes to auth state changes. This hook provides reactive auth state that your screens can use to conditionally render content or navigate between auth and app screens.
1// src/hooks/useAuth.ts2import { useEffect, useState } from 'react'3import { User } from '@supabase/supabase-js'4import { supabase } from '../lib/supabase'56export function useAuth() {7 const [user, setUser] = useState<User | null>(null)8 const [loading, setLoading] = useState(true)910 useEffect(() => {11 // Check current session12 supabase.auth.getSession().then(({ data: { session } }) => {13 setUser(session?.user ?? null)14 setLoading(false)15 })1617 // Listen to auth changes18 const { data: { subscription } } = supabase.auth.onAuthStateChange(19 (_event, session) => {20 setUser(session?.user ?? null)21 }22 )2324 return () => subscription.unsubscribe()25 }, [])2627 return { user, loading, isAuthenticated: !!user }28}Expected result: A reusable hook that provides current user state, loading indicator, and auth status to any React Native component.
Perform database CRUD operations
Perform database CRUD operations
Query and modify Supabase tables using the same API as web React. The from() method works identically in React Native. Make sure you have RLS policies on your tables since the anon key is embedded in the mobile app binary and can be extracted. All database calls automatically include the auth token from AsyncStorage.
1import { supabase } from '../lib/supabase'23// Fetch todos4export async function getTodos() {5 const { data, error } = await supabase6 .from('todos')7 .select('*')8 .order('created_at', { ascending: false })9 if (error) throw error10 return data11}1213// Add a new todo14export async function addTodo(title: string) {15 const { data: { user } } = await supabase.auth.getUser()16 if (!user) throw new Error('Not authenticated')1718 const { data, error } = await supabase19 .from('todos')20 .insert({ title, is_complete: false, user_id: user.id })21 .select()22 .single()23 if (error) throw error24 return data25}2627// Update a todo28export async function updateTodo(id: number, updates: Record<string, unknown>) {29 const { data, error } = await supabase30 .from('todos')31 .update(updates)32 .eq('id', id)33 .select()34 .single()35 if (error) throw error36 return data37}Expected result: Your React Native app can create, read, update, and delete data from Supabase with auth tokens automatically attached.
Set up RLS policies for mobile security
Set up RLS policies for mobile security
Mobile apps require careful RLS configuration because the anon key is bundled into the app binary. Anyone can decompile the app and extract the key. RLS is your primary security layer — it ensures that even with the key, users can only access their authorized data. Enable RLS on every table and write policies that check auth.uid().
1-- Enable RLS on the todos table2alter table public.todos enable row level security;34-- Authenticated users can read their own todos5create policy "Users read own todos"6 on public.todos for select7 to authenticated8 using ((select auth.uid()) = user_id);910-- Authenticated users can create their own todos11create policy "Users create own todos"12 on public.todos for insert13 to authenticated14 with check ((select auth.uid()) = user_id);1516-- Authenticated users can update their own todos17create policy "Users update own todos"18 on public.todos for update19 to authenticated20 using ((select auth.uid()) = user_id);2122-- Authenticated users can delete their own todos23create policy "Users delete own todos"24 on public.todos for delete25 to authenticated26 using ((select auth.uid()) = user_id);Expected result: All table access is secured at the row level. Users can only interact with their own data regardless of how they access the API.
Complete working example
1// src/lib/supabase.ts2import AsyncStorage from '@react-native-async-storage/async-storage'3import { createClient } from '@supabase/supabase-js'45const supabaseUrl = 'https://your-project-ref.supabase.co'6const supabaseAnonKey = 'your-anon-key-here'78export const supabase = createClient(supabaseUrl, supabaseAnonKey, {9 auth: {10 storage: AsyncStorage,11 autoRefreshToken: true,12 persistSession: true,13 detectSessionInUrl: false,14 },15})1617// Auth functions18export async function signIn(email: string, password: string) {19 const { data, error } = await supabase.auth.signInWithPassword({ email, password })20 if (error) throw error21 return data22}2324export async function signUp(email: string, password: string) {25 const { data, error } = await supabase.auth.signUp({ email, password })26 if (error) throw error27 return data28}2930export async function signOut() {31 const { error } = await supabase.auth.signOut()32 if (error) throw error33}3435// Database functions36export async function getTodos() {37 const { data, error } = await supabase38 .from('todos')39 .select('*')40 .order('created_at', { ascending: false })41 if (error) throw error42 return data43}4445export async function addTodo(title: string) {46 const { data: { user } } = await supabase.auth.getUser()47 if (!user) throw new Error('Not authenticated')48 const { data, error } = await supabase49 .from('todos')50 .insert({ title, is_complete: false, user_id: user.id })51 .select()52 .single()53 if (error) throw error54 return data55}5657export async function toggleTodo(id: number, isComplete: boolean) {58 const { data, error } = await supabase59 .from('todos')60 .update({ is_complete: isComplete })61 .eq('id', id)62 .select()63 .single()64 if (error) throw error65 return data66}6768export async function deleteTodo(id: number) {69 const { error } = await supabase70 .from('todos')71 .delete()72 .eq('id', id)73 if (error) throw error74}Common mistakes when connecting Supabase to React Native
Why it's a problem: Not installing or configuring AsyncStorage, causing session persistence to fail silently
How to avoid: Install @react-native-async-storage/async-storage and pass it as the storage option in createClient. Without it, sessions are lost on every app restart.
Why it's a problem: Leaving detectSessionInUrl set to true (the default), which causes errors in React Native
How to avoid: Set detectSessionInUrl: false in the auth config. URL-based session detection is a browser feature that does not work in React Native.
Why it's a problem: Not enabling RLS, leaving data accessible to anyone who extracts the anon key from the app binary
How to avoid: Always enable RLS on every table and write policies. The anon key in a mobile app can be extracted by decompiling the binary.
Why it's a problem: Importing the Supabase client from multiple files instead of a single shared module
How to avoid: Create one supabase.ts file that exports the client instance. Import from that file everywhere to ensure a single client manages the session.
Best practices
- Create a single Supabase client instance in a shared module and import it throughout the app
- Configure AsyncStorage as the storage backend and set detectSessionInUrl to false
- Enable autoRefreshToken and persistSession for seamless auth across app restarts
- Always enable RLS on tables — mobile apps expose the anon key in the app binary
- Use a custom useAuth hook to provide reactive auth state to all components
- Implement pagination with .range() for lists to avoid loading too much data on mobile
- Handle network errors gracefully — mobile apps frequently go offline
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to connect my React Native app to Supabase. Show me how to set up the client with AsyncStorage, implement email/password auth, create a useAuth hook, and perform CRUD operations on a todos table.
Help me integrate Supabase into my React Native project. I need the client setup with AsyncStorage for session persistence, auth functions, a custom hook for tracking auth state, and database query examples with proper RLS policies.
Frequently asked questions
Does the Supabase JavaScript client work in React Native?
Yes. The @supabase/supabase-js package works in React Native. The only required change is configuring AsyncStorage as the session storage backend instead of localStorage.
How does session persistence work on mobile?
The Supabase client stores the auth session in AsyncStorage, which persists data on the device's file system. The client automatically restores the session on app launch and refreshes expired tokens.
Can I use OAuth (Google, Apple) with Supabase in React Native?
Yes, but it requires additional setup for deep links. Configure URL schemes in your app.json (Expo) or Info.plist/AndroidManifest (bare RN). Use supabase.auth.signInWithOAuth() with a redirectTo URL that matches your deep link scheme.
Should I use Expo or bare React Native with Supabase?
Both work equally well. Expo is easier to set up and AsyncStorage is included in the SDK. Bare React Native gives more control but requires manual native module linking.
Can I use Supabase Realtime in React Native?
Yes. Supabase Realtime uses WebSockets which work in React Native. Subscribe to channels the same way as in web React — the API is identical.
Can RapidDev help build a React Native app with Supabase?
Yes. RapidDev can help you build mobile applications with React Native and Supabase, including auth flows, database design, offline support, and app store deployment.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation