Skip to main content
RapidDev - Software Development Agency
supabase-tutorial

How to Connect Supabase to React Native

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.

What you'll learn

  • How to install and configure the Supabase client for React Native
  • How to set up AsyncStorage for session persistence on mobile
  • How to implement auth and CRUD operations in React Native
  • How to handle auth state changes with React hooks
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read15-20 minSupabase (all plans), @supabase/supabase-js v2+, React Native 0.72+, Expo SDK 49+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1# For Expo projects
2npx expo install @supabase/supabase-js @react-native-async-storage/async-storage
3
4# For bare React Native projects
5npm install @supabase/supabase-js @react-native-async-storage/async-storage
6npx pod-install # iOS only

Expected result: Both packages are installed and ready to import in your React Native code.

2

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.

typescript
1// src/lib/supabase.ts
2import AsyncStorage from '@react-native-async-storage/async-storage'
3import { createClient } from '@supabase/supabase-js'
4
5const supabaseUrl = 'https://your-project-ref.supabase.co'
6const supabaseAnonKey = 'your-anon-key-here'
7
8export 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.

3

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.

typescript
1import { supabase } from '../lib/supabase'
2
3export async function signUp(email: string, password: string) {
4 const { data, error } = await supabase.auth.signUp({
5 email,
6 password,
7 })
8 if (error) throw error
9 return data
10}
11
12export async function signIn(email: string, password: string) {
13 const { data, error } = await supabase.auth.signInWithPassword({
14 email,
15 password,
16 })
17 if (error) throw error
18 return data
19}
20
21export async function signOut() {
22 const { error } = await supabase.auth.signOut()
23 if (error) throw error
24}

Expected result: Users can sign up, sign in, and sign out. The session persists in AsyncStorage between app restarts.

4

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.

typescript
1// src/hooks/useAuth.ts
2import { useEffect, useState } from 'react'
3import { User } from '@supabase/supabase-js'
4import { supabase } from '../lib/supabase'
5
6export function useAuth() {
7 const [user, setUser] = useState<User | null>(null)
8 const [loading, setLoading] = useState(true)
9
10 useEffect(() => {
11 // Check current session
12 supabase.auth.getSession().then(({ data: { session } }) => {
13 setUser(session?.user ?? null)
14 setLoading(false)
15 })
16
17 // Listen to auth changes
18 const { data: { subscription } } = supabase.auth.onAuthStateChange(
19 (_event, session) => {
20 setUser(session?.user ?? null)
21 }
22 )
23
24 return () => subscription.unsubscribe()
25 }, [])
26
27 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.

5

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.

typescript
1import { supabase } from '../lib/supabase'
2
3// Fetch todos
4export async function getTodos() {
5 const { data, error } = await supabase
6 .from('todos')
7 .select('*')
8 .order('created_at', { ascending: false })
9 if (error) throw error
10 return data
11}
12
13// Add a new todo
14export async function addTodo(title: string) {
15 const { data: { user } } = await supabase.auth.getUser()
16 if (!user) throw new Error('Not authenticated')
17
18 const { data, error } = await supabase
19 .from('todos')
20 .insert({ title, is_complete: false, user_id: user.id })
21 .select()
22 .single()
23 if (error) throw error
24 return data
25}
26
27// Update a todo
28export async function updateTodo(id: number, updates: Record<string, unknown>) {
29 const { data, error } = await supabase
30 .from('todos')
31 .update(updates)
32 .eq('id', id)
33 .select()
34 .single()
35 if (error) throw error
36 return data
37}

Expected result: Your React Native app can create, read, update, and delete data from Supabase with auth tokens automatically attached.

6

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().

typescript
1-- Enable RLS on the todos table
2alter table public.todos enable row level security;
3
4-- Authenticated users can read their own todos
5create policy "Users read own todos"
6 on public.todos for select
7 to authenticated
8 using ((select auth.uid()) = user_id);
9
10-- Authenticated users can create their own todos
11create policy "Users create own todos"
12 on public.todos for insert
13 to authenticated
14 with check ((select auth.uid()) = user_id);
15
16-- Authenticated users can update their own todos
17create policy "Users update own todos"
18 on public.todos for update
19 to authenticated
20 using ((select auth.uid()) = user_id);
21
22-- Authenticated users can delete their own todos
23create policy "Users delete own todos"
24 on public.todos for delete
25 to authenticated
26 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

supabase.ts
1// src/lib/supabase.ts
2import AsyncStorage from '@react-native-async-storage/async-storage'
3import { createClient } from '@supabase/supabase-js'
4
5const supabaseUrl = 'https://your-project-ref.supabase.co'
6const supabaseAnonKey = 'your-anon-key-here'
7
8export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
9 auth: {
10 storage: AsyncStorage,
11 autoRefreshToken: true,
12 persistSession: true,
13 detectSessionInUrl: false,
14 },
15})
16
17// Auth functions
18export async function signIn(email: string, password: string) {
19 const { data, error } = await supabase.auth.signInWithPassword({ email, password })
20 if (error) throw error
21 return data
22}
23
24export async function signUp(email: string, password: string) {
25 const { data, error } = await supabase.auth.signUp({ email, password })
26 if (error) throw error
27 return data
28}
29
30export async function signOut() {
31 const { error } = await supabase.auth.signOut()
32 if (error) throw error
33}
34
35// Database functions
36export async function getTodos() {
37 const { data, error } = await supabase
38 .from('todos')
39 .select('*')
40 .order('created_at', { ascending: false })
41 if (error) throw error
42 return data
43}
44
45export 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 supabase
49 .from('todos')
50 .insert({ title, is_complete: false, user_id: user.id })
51 .select()
52 .single()
53 if (error) throw error
54 return data
55}
56
57export async function toggleTodo(id: number, isComplete: boolean) {
58 const { data, error } = await supabase
59 .from('todos')
60 .update({ is_complete: isComplete })
61 .eq('id', id)
62 .select()
63 .single()
64 if (error) throw error
65 return data
66}
67
68export async function deleteTodo(id: number) {
69 const { error } = await supabase
70 .from('todos')
71 .delete()
72 .eq('id', id)
73 if (error) throw error
74}

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.

ChatGPT Prompt

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.

Supabase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.