To add Google Sign-In to a Capacitor app, create a Web, an iOS and one or more Android OAuth client IDs in Google Cloud, install @capgo/capacitor-social-login, call SocialLogin.initialize({ google: { webClientId, iOSClientId } }), then SocialLogin.login({ provider: 'google' }). Android uses Google’s Credential Manager, iOS uses the Google Sign-In SDK, and the web uses Google Identity Services. All three return an ID token you verify on your server.
This guide walks through every step for Capacitor 8 in October 2026, including the parts that usually break: SHA-1 fingerprints, the Play App Signing key, and the iOS URL scheme.
How Google Sign-In works in a Capacitor app
| Platform | Native API used by the plugin | Client ID passed to the plugin | Other console setup |
|---|---|---|---|
| Android | Credential Manager (androidx.credentials) |
Web client ID as webClientId |
Android client with package name and SHA-1 |
| iOS | Google Sign-In SDK | iOS client ID as iOSClientId |
Reversed client ID URL scheme in Info.plist |
| Web | Google Identity Services | Web client ID as webClientId |
Authorized JavaScript origins |
Google blocks OAuth inside embedded WebViews with the disallowed_useragent error, which is why loading Google’s login page inside your Capacitor WebView does not work. The plugin uses the native account picker instead.
Step 1: Configure Google Cloud
Create a project and the Google Auth Platform
- Open Google Cloud Console and create a project, or pick an existing one. All client IDs must live in the same project.
- Open Google Auth Platform (the screen formerly called “OAuth consent screen”).
- Under Branding, set the app name users will see, a support email, and your domains and privacy policy URL.
- Under Audience, choose External for consumer apps. While the app is in Testing, only the accounts listed under Test users can sign in.
- Under Data Access, keep the default
openid,emailandprofilescopes unless you need more.
You do not need to publish to production or pass verification for the basic scopes. Sensitive scopes (Calendar, Drive, Gmail) need verification before public release.
Create the client IDs
Go to Google Auth Platform > Clients (or APIs & Services > Credentials) and create:
| Client type | What to enter | Where the ID goes |
|---|---|---|
| Web application | Authorized JavaScript origins for your web build (for example https://app.example.com, http://localhost:5173) |
webClientId |
| iOS | Your bundle ID, plus App Store ID and Team ID once you have them | iOSClientId |
| Android (debug) | Package name plus debug SHA-1 | Console only |
| Android (release) | Package name plus upload key SHA-1 | Console only |
| Android (Play) | Package name plus Play App Signing SHA-1 | Console only |
Get the SHA-1 values:
# Debug key and any signing config in build.gradle
cd android && ./gradlew signingReport
# From a signed APK you actually install
keytool -printcert -jarfile android/app/build/outputs/apk/release/app-release.apk
For Play Store builds, copy the SHA-1 from Play Console > Test and release > App integrity > App signing key certificate. If you need a release keystore first, the Android keystore generator creates one in the browser. Our Google client ID guide goes deeper on the console screens.
Step 2: Install the plugin
bun add @capgo/capacitor-social-login
bunx cap sync
Disable providers you do not use so their SDKs stay out of the binary:
// capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
plugins: {
SocialLogin: {
providers: {
google: true,
apple: true,
facebook: false,
twitter: false,
},
},
},
};
export default config;
Run bunx cap sync after changing providers.
Step 3: iOS configuration
Add the reversed client ID URL scheme
Open the iOS client in Google Cloud and copy the iOS URL scheme, which looks like com.googleusercontent.apps.1234567890-abcdef. Add it to ios/App/App/Info.plist before the closing </dict>:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.1234567890-abcdef</string>
</array>
</dict>
</array>
If you already have a CFBundleURLTypes array for deep links, add a new <dict> entry to it instead of creating a second key.
Let the Google SDK handle the callback URL
In ios/App/App/AppDelegate.swift, import the SDK and route the URL to it before Capacitor:
import GoogleSignIn // add at the top of the file
// Replace the generated application(_:open:options:) method
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if GIDSignIn.sharedInstance.handle(url) {
return true
}
return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
}
Keep the ApplicationDelegateProxy call so @capacitor/app still receives your other deep links.
Step 4: Android configuration
Basic sign-in with the default scopes works without native changes. If you request extra scopes or use offline mode, the plugin needs to receive activity results, so MainActivity must implement the plugin’s marker interface:
package com.example.app;
import android.content.Intent;
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginHandle;
import ee.forgr.capacitor.social.login.GoogleProvider;
import ee.forgr.capacitor.social.login.ModifiedMainActivityForSocialLoginPlugin;
import ee.forgr.capacitor.social.login.SocialLoginPlugin;
public class MainActivity extends BridgeActivity implements ModifiedMainActivityForSocialLoginPlugin {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode >= GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MIN
&& requestCode < GoogleProvider.REQUEST_AUTHORIZE_GOOGLE_MAX) {
PluginHandle handle = getBridge().getPlugin("SocialLogin");
if (handle == null) return;
Plugin plugin = handle.getInstance();
if (plugin instanceof SocialLoginPlugin) {
((SocialLoginPlugin) plugin).handleGoogleLoginIntent(requestCode, data);
}
}
}
@Override
public void IHaveModifiedTheMainActivityForTheUseWithSocialLoginPlugin() {}
}
Without it, calls with scopes reject with “You CANNOT use scopes without modifying the main activity”.
For the emulator, use a system image with the Google Play label and sign in to a Google account in device settings. Images without Play services return NoCredentialException.
Step 5: Web configuration
Add every origin you serve the web build from to the Web client’s Authorized JavaScript origins, including the port (http://localhost:5173). Call initialize early: the plugin injects Google’s script tag and the login fails if it runs before the script is ready.
Step 6: Initialize and sign in
import { SocialLogin } from '@capgo/capacitor-social-login';
const WEB_CLIENT_ID = '1234567890-web.apps.googleusercontent.com';
const IOS_CLIENT_ID = '1234567890-ios.apps.googleusercontent.com';
export async function initAuth() {
await SocialLogin.initialize({
google: {
webClientId: WEB_CLIENT_ID, // Android + web
iOSClientId: IOS_CLIENT_ID, // iOS
iOSServerClientId: WEB_CLIENT_ID, // same as webClientId, required for offline mode on iOS
mode: 'online',
},
});
}
export async function signInWithGoogle() {
try {
const { result } = await SocialLogin.login({
provider: 'google',
options: {},
});
if (result.responseType !== 'online' || !result.idToken) {
throw new Error('Expected an ID token from Google');
}
const res = await fetch('https://api.example.com/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken: result.idToken }),
});
return res.json();
} catch (error: any) {
if (error?.code === 'USER_CANCELLED') return null;
throw error;
}
}
The online response contains:
| Field | Notes |
|---|---|
idToken |
OpenID Connect JWT. Send this to your server |
accessToken |
For calling Google APIs from the client. Can be null on Android when only default scopes are requested |
profile |
email, name, givenName, familyName, imageUrl, id |
responseType |
'online' |
Android-only display options
The options object accepts style: 'bottom' for the bottom sheet UI, plus filterByAuthorizedAccounts and autoSelectEnabled for returning users. Leave filterByAuthorizedAccounts off if you support Family Link accounts. forceRefreshToken: true asks Android for a fresh access token instead of a cached one.
Requesting extra scopes
const { result } = await SocialLogin.login({
provider: 'google',
options: {
scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
},
});
Remember the MainActivity change on Android, and add the scope under Data Access in Google Cloud.
Online vs offline mode
| Online (default) | Offline | |
|---|---|---|
| Returns | ID token, access token, profile | serverAuthCode only |
| Backend needed | Only to verify the token | Yes, to exchange the code |
| Google refresh token | Stays with Google SDK on device | Stored on your server |
| Use when | You only need to know who the user is | Your server calls Google APIs while the user is away |
logout, isLoggedIn, refresh |
Supported | Not supported |
Offline mode on iOS requires iOSServerClientId, and on Android it requires the MainActivity change.
Logout and session checks
await SocialLogin.isLoggedIn({ provider: 'google' }); // { isLoggedIn: boolean }
await SocialLogin.logout({ provider: 'google' });
On Android, logout also clears the Credential Manager state, so the account picker appears again next time.
Step 7: Verify the ID token on your server
import { OAuth2Client } from 'google-auth-library';
const client = new OAuth2Client();
export async function verifyGoogleToken(idToken: string) {
const ticket = await client.verifyIdToken({
idToken,
audience: [
'1234567890-web.apps.googleusercontent.com',
'1234567890-ios.apps.googleusercontent.com',
],
});
const payload = ticket.getPayload();
if (!payload) throw new Error('Empty token payload');
return {
googleUserId: payload.sub,
email: payload.email,
emailVerified: payload.email_verified === true,
name: payload.name,
};
}
Use sub as the stable user key. Only link an existing account by email when email_verified is true. Accepting both client IDs as audience keeps tokens from every platform valid. Then issue your own session. If you use a managed backend, follow Supabase with Capacitor Social Login or the Firebase Google guide.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
[28444] Developer console is not set up correctly |
Package name, SHA-1 or webClientId mismatch |
Use the Web client ID, register the SHA-1 of the installed build, keep all clients in one project |
| Works in debug, fails from Play Store | Play re-signs the app | Add an Android client with the Play App Signing SHA-1 |
[16] Account reauth failed |
Cached account state is stale, account not a test user, or user disabled the app | The plugin retries once automatically. Check test users, External audience, and Play signing SHA-1 |
NoCredentialException |
No Google account on device, or emulator without Play services | Use a Google Play system image and sign in |
USER_CANCELLED right after picking an account |
Usually still a SHA-1 or client ID mismatch | Fix console setup before treating it as a real cancel |
| iOS crash: “missing support for the following URL schemes” | Reversed client ID missing in Info.plist |
Add the com.googleusercontent.apps... scheme |
| Web: “The given origin is not allowed for the given client ID” | Origin missing on the Web client | Add the exact origin, including port |
access_denied with “app has not completed the Google verification process” |
Account is not a test user while in Testing | Add it under Audience > Test users |
| Google login screen interrupted on iOS | @capacitor/privacy-screen covers the SDK view |
Call PrivacyScreen.disable() before login |
On Android, filter Logcat by GoogleProvider. The plugin logs the package name, signing SHA-1 and a masked webClientId, which you can compare against the console in a minute.
Shipping and maintaining
Console changes can take a few hours to propagate, so do not rotate client IDs on release day. Once the native setup is in a store build, client-side changes such as scopes, error handling or the backend URL can go out through Capgo live updates. If your team does not have a Mac for iOS releases, Capgo Build builds and signs the iOS binary in the cloud.
Related guides
- How to sign in with Apple using Capacitor, which you will need on iOS if Google is your main login.
- How to add authentication to a Capacitor app for choosing between social login, OIDC, passkeys and Better Auth.
- Social Login plugin page and the Google Android setup docs.