Supabase Flutter deep link not working: scheme, Info.plist, AndroidManifest and the underscore bug
You call signInWithOAuth(), sign in with Google, and the browser never hands control back to the app. Instead it sits on http://localhost:3000/, or Android logs ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=your_app://login-callback }, while the Flutter app is still showing the sign-in screen. supabase_flutter needs five places to agree on the exact same scheme and host: your Dart code, Info.plist, AndroidManifest.xml, the Supabase redirect allow-list, and the OAuth provider console. Here is each place it breaks, one at a time.
01
The scheme or host has an underscore
If app.scheme or app.host contains an underscore, for example my_app://callback, the redirect fails even when Info.plist, AndroidManifest and the Supabase allow-list all list it correctly. Google's OAuth redirect handling mangles underscores in redirect URLs: a scheme or host like my_app always fails Supabase's redirect-URL check and silently falls back to the Site URL. This is tracked upstream as supabase/auth#2447 and there is no server-side fix. The underscore itself is the bug, so rename it to a hyphen everywhere it appears.
// before
scheme: "my_app"
redirectTo: "my_app://login-callback"
// after
scheme: "my-app"
redirectTo: "my-app://login-callback"
02
The scheme itself is not valid
A custom URL scheme must start with a letter and use only letters, digits, +, - and .. No spaces, and no case mismatch: schemes are matched case-sensitively at OS registration level, so io.supabase.MyApp and io.supabase.myapp are two different schemes to Android and iOS. And http or https are not custom schemes at all: those only work through iOS Universal Links or Android App Links, which need apple-app-site-association, assetlinks.json and android:autoVerify on a real https host, a separate setup from what is described here. A safe default is a reverse-domain identifier such as io.supabase.myapp.
03
Info.plist does not list the scheme
CFBundleURLSchemes in ios/Runner/Info.plist has to contain the exact scheme. Without it, iOS has nothing registered to hand the redirect to: Safari, or the in-app browser tab the OAuth screen opened, just sits on the URL, and the OS may report that it cannot open the link at all.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>io.supabase.myapp</string>
</array>
</dict>
</array>
04
AndroidManifest's intent-filter is missing or does not match
Android needs an <intent-filter> on the launcher activity with android:scheme, and android:host if your redirect uses one, equal to app.scheme / app.host. Missing it entirely produces ActivityNotFoundException in logcat. A mismatched scheme or host, often left over from an old value, fails the same way but silently: nothing in the manifest is wrong on its own, it just never fires for this redirect. One more detail: android:autoVerify="true" does nothing on a custom-scheme filter. It only triggers Digital Asset Links verification for https App Links, so leave it off here.
<intent-filter android:autoVerify="false">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="io.supabase.myapp" android:host="login-callback" />
</intent-filter>
05
redirectTo is not on the Supabase allow-list, or does not match exactly
Whatever string signInWithOAuth(redirectTo: ...) sends has to equal scheme://host byte for byte, trailing slash included, and it has to be covered by an entry under Supabase → Authentication → URL Configuration → Redirect URLs, either the exact string or a glob such as io.supabase.myapp://**. If it is not covered, Supabase does not throw an error: it silently falls back to the Site URL, so the browser ends up in your web app, or on localhost if that is still what Site URL points to, instead of back in the mobile app. The failure looks identical to every other cause on this page, which is why it is worth checking the allow-list first.
06
The provider console has the wrong redirect URI
Google, Apple, GitHub, Discord and Kakao all need the Supabase project's own callback in their console, not your app's scheme: https://<project-ref>.supabase.co/auth/v1/callback, an exact match, no wildcards accepted. The handshake goes provider to Supabase to app: the provider redirects to Supabase first, and only Supabase then redirects on to your app's custom scheme. Putting io.supabase.myapp://login-callback in the provider's own settings breaks the first hop, before your app's scheme is even involved.
Checklist
- Scheme and host use only lowercase letters, digits,
-and.: no underscores, no spaces. - Scheme is a custom scheme, not
httporhttps, unless Universal Links / App Links are set up separately. - Info.plist's
CFBundleURLSchemescontains the exact scheme. - AndroidManifest's intent-filter scheme, and host if used, matches exactly, with no
android:autoVerifyon it. redirectToin code equalsscheme://hostexactly, including the trailing slash.- That value, or a matching pattern, is on Supabase's Redirect URLs list.
- Supabase's Site URL is a real production URL, not
localhost. - The OAuth provider console has the exact
https://<ref>.supabase.co/auth/v1/callback. authFlowTypeispkceandauthScreenLaunchModeisLaunchMode.externalApplication.onAuthStateChangehas a listener, so the UI updates once the session comes back.
Check it in 30 seconds
Paste your scheme, host, Info.plist, AndroidManifest and Supabase settings into the Supabase Deep Link Doctor for Flutter and it lists every mismatch by name, with the exact value to fix.
Sources
- Supabase Docs, Native mobile deep linking: redirect URL format, CFBundleURLSchemes, AndroidManifest intent-filter.
- Supabase Docs, signInWithOAuth (Dart reference): redirectTo, authScreenLaunchMode, LaunchMode.externalApplication.
- pub.dev, supabase_flutter package page: deep links for magic link, e-mail confirmation, password reset and OAuth.
- GitHub, supabase/auth issue #2447: underscores in a redirect URL always fail the check and fall back to Site URL.