Connecting Mobile Apps to Self-Hosted Supabase: React Native and Flutter Guide

Learn how to configure React Native and Flutter apps to connect with your self-hosted Supabase instance for complete mobile backend control.

Cover Image for Connecting Mobile Apps to Self-Hosted Supabase: React Native and Flutter Guide

Running a self-hosted Supabase instance gives you complete control over your backend infrastructure. But when it comes to connecting mobile apps, the configuration differs from Supabase Cloud in ways that catch many developers off guard. Whether you're building with React Native or Flutter, this guide walks through the exact steps to connect your mobile app to a self-hosted Supabase deployment.

Why Self-Hosted Supabase for Mobile Apps

Mobile developers choose self-hosted Supabase for several reasons: data residency requirements, predictable costs at scale, or simply wanting complete control over their backend. With Supascale, you can deploy and manage self-hosted Supabase instances without the operational overhead that typically comes with DIY deployments.

The trade-off is real, though. Self-hosting means your team handles infrastructure, security hardening, and backups. For mobile teams without dedicated DevOps resources, a management layer like Supascale bridges that gap by automating the operational tasks while preserving full control.

Prerequisites

Before connecting your mobile app, ensure your self-hosted Supabase instance is properly configured:

  • HTTPS enabled: Mobile apps require secure connections. Set up custom domains with SSL certificates before proceeding.
  • API Gateway accessible: Your Kong or Envoy API gateway should be reachable from the internet (or your internal network for enterprise deployments).
  • Authentication configured: If using OAuth providers, ensure they're configured in your self-hosted instance with proper redirect URLs.

Gathering Your Connection Details

Self-hosted Supabase requires three key values that differ from Supabase Cloud:

1. Supabase URL

Your Supabase URL is the address of your API gateway, not the Studio dashboard:

https://api.yourdomain.com

If you're running on a VPS without a custom domain:

http://YOUR_SERVER_IP:8000

Important: Using an IP address with HTTP works for development but is not recommended for production mobile apps. App stores and platform security features increasingly require HTTPS.

2. Anon Key

Find your anonymous (public) key in your .env file:

ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

This key is safe to embed in client applications. Row Level Security (RLS) policies protect your data regardless of who has the anon key.

3. API Key Transition (2026)

Supabase is transitioning to new API key formats. The older anon and service_role keys work until the end of 2026, but new deployments should use the sb_publishable_xxx format. Check your Supascale dashboard or API Keys settings if you've generated new keys.

React Native Configuration

Installing the Supabase SDK

For Expo-based React Native projects:

npx expo install @supabase/supabase-js @react-native-async-storage/async-storage

For bare React Native:

npm install @supabase/supabase-js @react-native-async-storage/async-storage

Creating the Supabase Client

Create lib/supabase.ts in your project:

import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = 'https://api.yourdomain.com';
const supabaseAnonKey = 'your-anon-key';

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

Key differences from Supabase Cloud:

  • supabaseUrl points to your self-hosted API gateway, not *.supabase.co
  • detectSessionInUrl: false prevents issues with deep linking on mobile
  • autoRefreshToken: true maintains sessions across app restarts

Environment Variables

Never hardcode credentials. Use environment variables with Expo:

// app.config.js
export default {
  expo: {
    extra: {
      supabaseUrl: process.env.SUPABASE_URL,
      supabaseAnonKey: process.env.SUPABASE_ANON_KEY,
    },
  },
};

Then access them:

import Constants from 'expo-constants';

const supabaseUrl = Constants.expoConfig?.extra?.supabaseUrl;
const supabaseAnonKey = Constants.expoConfig?.extra?.supabaseAnonKey;

Deep Linking for Authentication

Magic links and OAuth redirects require proper deep linking. Configure your app.json:

{
  "expo": {
    "scheme": "yourapp",
    "ios": {
      "bundleIdentifier": "com.yourcompany.yourapp"
    },
    "android": {
      "package": "com.yourcompany.yourapp"
    }
  }
}

In your self-hosted Supabase, add the redirect URL to your Auth settings:

yourapp://auth-callback

Handle the callback in your app:

import * as Linking from 'expo-linking';
import { supabase } from './lib/supabase';

Linking.addEventListener('url', async (event) => {
  const url = event.url;
  if (url.includes('auth-callback')) {
    const { data, error } = await supabase.auth.getSessionFromUrl({ 
      storeSession: true 
    });
  }
});

Flutter Configuration

Installing the Supabase SDK

Add to your pubspec.yaml:

dependencies:
  supabase_flutter: ^2.5.0

Run:

flutter pub get

Initializing Supabase

In your main.dart:

import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Supabase.initialize(
    url: 'https://api.yourdomain.com',
    anonKey: 'your-anon-key',
  );

  runApp(const MyApp());
}

final supabase = Supabase.instance.client;

Platform-Specific Configuration

iOS (Info.plist)

For deep linking on iOS, add to ios/Runner/Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>yourapp</string>
    </array>
  </dict>
</array>

Android (AndroidManifest.xml)

Add an intent filter to android/app/src/main/AndroidManifest.xml:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="yourapp" android:host="auth-callback" />
</intent-filter>

macOS Entitlements

For macOS builds, update macos/Runner/DebugProfile.entitlements:

<key>com.apple.security.network.client</key>
<true/>

Handling Auth State

Flutter's Supabase SDK provides auth state listeners:

supabase.auth.onAuthStateChange.listen((data) {
  final AuthChangeEvent event = data.event;
  final Session? session = data.session;

  if (event == AuthChangeEvent.signedIn) {
    // Navigate to home screen
  } else if (event == AuthChangeEvent.signedOut) {
    // Navigate to login screen
  }
});

Common Issues and Solutions

Certificate Errors

Self-signed certificates cause connection failures on mobile. Solutions:

  1. Use Let's Encrypt: Free, trusted certificates that work everywhere
  2. Use Supascale: Automatic SSL provisioning with Let's Encrypt
  3. Development workaround: For local testing only, you can disable certificate verification (never in production)

WebSocket Connection Drops

Realtime features require WebSocket support. If connections drop frequently:

  • Verify your reverse proxy forwards WebSocket upgrade headers
  • Check timeout settings (Nginx default is 60 seconds)
  • Ensure your firewall allows WebSocket traffic

OAuth Redirect Failures

OAuth redirects failing usually means:

  1. Redirect URL mismatch: The URL in your OAuth provider console must exactly match your app's scheme
  2. Missing site URL: Configure GOTRUE_SITE_URL in your self-hosted Supabase to match your mobile app's redirect
  3. Custom domain required: Some OAuth providers (like Google) require verified domains. See our guide on Google OAuth without custom domains

Connection Timeouts on Cellular Networks

Mobile networks can be unreliable. Configure retry logic:

// React Native
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
  global: {
    fetch: (url, options) => {
      return fetch(url, {
        ...options,
        timeout: 30000, // 30 second timeout
      });
    },
  },
});

Testing Your Connection

Basic Connectivity Test

// React Native / TypeScript
const testConnection = async () => {
  try {
    const { data, error } = await supabase.from('your_table').select('count');
    if (error) throw error;
    console.log('Connected successfully');
  } catch (error) {
    console.error('Connection failed:', error.message);
  }
};
// Flutter
Future<void> testConnection() async {
  try {
    final response = await supabase.from('your_table').select('count');
    print('Connected successfully');
  } catch (error) {
    print('Connection failed: $error');
  }
}

Auth Flow Test

Test authentication before building out your app:

const testAuth = async () => {
  const { data, error } = await supabase.auth.signInWithPassword({
    email: '[email protected]',
    password: 'testpassword',
  });

  if (error) {
    console.error('Auth failed:', error.message);
  } else {
    console.log('Auth successful:', data.user?.email);
  }
};

Production Checklist

Before shipping your mobile app with a self-hosted Supabase backend:

  • [ ] HTTPS configured with valid SSL certificates
  • [ ] Row Level Security enabled on all tables
  • [ ] Rate limiting configured to prevent abuse
  • [ ] Backups automated with Supascale's backup system
  • [ ] Deep links tested on both iOS and Android
  • [ ] OAuth providers verified with correct redirect URLs
  • [ ] Error handling implemented for network failures
  • [ ] API keys secured using environment variables, not hardcoded

Next Steps

With your mobile app connected to self-hosted Supabase, you can now:

  • Implement authentication with email/password, magic links, or social providers
  • Query your database using the auto-generated REST API
  • Upload files to Supabase Storage with RLS policies
  • Enable realtime updates for collaborative features

If you haven't deployed your self-hosted Supabase instance yet, get started with Supascale to skip the operational complexity. Our one-time purchase includes unlimited projects, automated backups, and a management UI that handles the infrastructure so you can focus on building your app.


Further Reading