# How to Connect Supabase to React Native

- Tool: Supabase
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Supabase (all plans), @supabase/supabase-js v2+, React Native 0.72+, Expo SDK 49+
- Last updated: March 2026

## 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.

## Before you start

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

```
# For Expo projects
npx expo install @supabase/supabase-js @react-native-async-storage/async-storage

# For bare React Native projects
npm install @supabase/supabase-js @react-native-async-storage/async-storage
npx 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.

```
// src/lib/supabase.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = 'https://your-project-ref.supabase.co'
const supabaseAnonKey = 'your-anon-key-here'

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
})
```

**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.

```
import { supabase } from '../lib/supabase'

export async function signUp(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  })
  if (error) throw error
  return data
}

export async function signIn(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  })
  if (error) throw error
  return data
}

export async function signOut() {
  const { error } = await supabase.auth.signOut()
  if (error) throw error
}
```

**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.

```
// src/hooks/useAuth.ts
import { useEffect, useState } from 'react'
import { User } from '@supabase/supabase-js'
import { supabase } from '../lib/supabase'

export function useAuth() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Check current session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null)
      setLoading(false)
    })

    // Listen to auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  return { user, loading, isAuthenticated: !!user }
}
```

**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.

```
import { supabase } from '../lib/supabase'

// Fetch todos
export async function getTodos() {
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .order('created_at', { ascending: false })
  if (error) throw error
  return data
}

// Add a new todo
export async function addTodo(title: string) {
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Not authenticated')

  const { data, error } = await supabase
    .from('todos')
    .insert({ title, is_complete: false, user_id: user.id })
    .select()
    .single()
  if (error) throw error
  return data
}

// Update a todo
export async function updateTodo(id: number, updates: Record<string, unknown>) {
  const { data, error } = await supabase
    .from('todos')
    .update(updates)
    .eq('id', id)
    .select()
    .single()
  if (error) throw error
  return data
}
```

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

```
-- Enable RLS on the todos table
alter table public.todos enable row level security;

-- Authenticated users can read their own todos
create policy "Users read own todos"
  on public.todos for select
  to authenticated
  using ((select auth.uid()) = user_id);

-- Authenticated users can create their own todos
create policy "Users create own todos"
  on public.todos for insert
  to authenticated
  with check ((select auth.uid()) = user_id);

-- Authenticated users can update their own todos
create policy "Users update own todos"
  on public.todos for update
  to authenticated
  using ((select auth.uid()) = user_id);

-- Authenticated users can delete their own todos
create policy "Users delete own todos"
  on public.todos for delete
  to authenticated
  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 code example

File: `supabase.ts`

```typescript
// src/lib/supabase.ts
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = 'https://your-project-ref.supabase.co'
const supabaseAnonKey = 'your-anon-key-here'

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
})

// Auth functions
export async function signIn(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({ email, password })
  if (error) throw error
  return data
}

export async function signUp(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({ email, password })
  if (error) throw error
  return data
}

export async function signOut() {
  const { error } = await supabase.auth.signOut()
  if (error) throw error
}

// Database functions
export async function getTodos() {
  const { data, error } = await supabase
    .from('todos')
    .select('*')
    .order('created_at', { ascending: false })
  if (error) throw error
  return data
}

export async function addTodo(title: string) {
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) throw new Error('Not authenticated')
  const { data, error } = await supabase
    .from('todos')
    .insert({ title, is_complete: false, user_id: user.id })
    .select()
    .single()
  if (error) throw error
  return data
}

export async function toggleTodo(id: number, isComplete: boolean) {
  const { data, error } = await supabase
    .from('todos')
    .update({ is_complete: isComplete })
    .eq('id', id)
    .select()
    .single()
  if (error) throw error
  return data
}

export async function deleteTodo(id: number) {
  const { error } = await supabase
    .from('todos')
    .delete()
    .eq('id', id)
  if (error) throw error
}
```

## Common mistakes

- **Not installing or configuring AsyncStorage, causing session persistence to fail silently** — undefined Fix: 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.
- **Leaving detectSessionInUrl set to true (the default), which causes errors in React Native** — undefined Fix: Set detectSessionInUrl: false in the auth config. URL-based session detection is a browser feature that does not work in React Native.
- **Not enabling RLS, leaving data accessible to anyone who extracts the anon key from the app binary** — undefined Fix: Always enable RLS on every table and write policies. The anon key in a mobile app can be extracted by decompiling the binary.
- **Importing the Supabase client from multiple files instead of a single shared module** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/supabase-tutorial/how-to-connect-supabase-to-react-native
© RapidDev — https://www.rapidevelopers.com/supabase-tutorial/how-to-connect-supabase-to-react-native
