Play Restore Keys · Guides
Google Play restore keys in Flutter apps
From April 2027, Google Play requires phone and tablet apps that sign users in to sign those users back in on a new device without asking. Google's requirement says: "Successful user sign in restoration is determined through the successful restore key retrieval." The calls are Android Kotlin APIs, so a Flutter app needs a small native bridge. On 10 October 2026 pub.dev had one Flutter plugin for restore keys, covered below.
This guide has the native code, the bridge and what your server must do. Every API name was checked against Google's implementation guide and the androidx reference pages on 11 October 2026. It is technical help, not a Google Play compliance review.
What you need
- Android 9 or later, Google Play services core 24220000 or later and androidx.credentials 1.5.0 or later (Google's minimums; Google recommends the latest stable library).
- compileSdk 35 or later in your Android build: androidx.credentials 1.5.0 requires it.
- A relying-party server. A restore key is a passkey-style (WebAuthn) key: your server sends the options, the phone creates or signs with the key, and your server checks the result.
- Restore keys are Android only. On iOS the bridge below does nothing.
1. The native calls (Kotlin, the same for every framework)
Add the dependencies to the Android module that holds the code (Kotlin DSL shown; in a Groovy build.gradle use implementation "..."; 1.5.0 or any later version):
dependencies {
implementation("androidx.credentials:credentials:1.5.0")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
}
Then one class wraps the three calls. Creating the key with cloud backup on is Google's recommendation. Without a Google Account backup or a screen lock that throws E2eeUnavailableException, so the class tries again with cloud backup off.
package com.example.restorekeys
import android.content.Context
import androidx.credentials.ClearCredentialStateRequest
import androidx.credentials.CreateRestoreCredentialRequest
import androidx.credentials.CreateRestoreCredentialResponse
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetRestoreCredentialOption
import androidx.credentials.RestoreCredential
import androidx.credentials.exceptions.NoCredentialException
import androidx.credentials.exceptions.restorecredential.E2eeUnavailableException
// The three restore key calls. Every JSON string comes from your server or goes back to it.
class RestoreKeyClient(private val context: Context) {
private val credentialManager = CredentialManager.create(context)
// After sign-in. creationOptionsJson: your server's PublicKeyCredentialCreationOptionsJSON.
// Returns the registration response JSON for your server to verify and store.
suspend fun create(creationOptionsJson: String): String {
val response = try {
credentialManager.createCredential(context, CreateRestoreCredentialRequest(creationOptionsJson, true))
} catch (e: E2eeUnavailableException) {
// no backup or no screen lock: keep the key on the device, a direct transfer still carries it
credentialManager.createCredential(context, CreateRestoreCredentialRequest(creationOptionsJson, false))
}
return (response as CreateRestoreCredentialResponse).responseJson
}
// On first launch. requestOptionsJson: your server's PublicKeyCredentialRequestOptionsJSON.
// Returns the authentication response JSON for your server to verify, or null if there is no restore key.
suspend fun get(requestOptionsJson: String): String? = try {
val request = GetCredentialRequest(listOf(GetRestoreCredentialOption(requestOptionsJson)))
(credentialManager.getCredential(context, request).credential as RestoreCredential).authenticationResponseJson
} catch (e: NoCredentialException) {
null
}
// On sign-out. Deletes the restore key from the device and from the backup.
suspend fun clear() {
credentialManager.clearCredentialState(
ClearCredentialStateRequest(ClearCredentialStateRequest.TYPE_CLEAR_RESTORE_CREDENTIAL)
)
}
}
2. Call it from Dart
Option A: the existing plugin
One Flutter plugin exists: android_restore_credentials (version 0.1.0, BSD-3-Clause). Its README shows three calls. It is client-side only, so you still need the server in step 3. Its README says it retries without cloud backup when E2eeUnavailableException is thrown. We have not tested it.
final plugin = AndroidRestoreCredentials();
// After sign-in
final attestation = await plugin.createRestoreKey(requestJson: requestJson);
// On first launch or BackupAgent restore
final assertion = await plugin.getRestoreKey(requestJson: requestJson);
// On sign-out
await plugin.clearRestoreKey();
Option B: your own platform channel
Put RestoreKeyClient in your app's android/ Kotlin sources and answer a method channel from your activity:
package com.example.app
import com.example.restorekeys.RestoreKeyClient
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
class MainActivity : FlutterActivity() {
private val scope = MainScope()
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val keys = RestoreKeyClient(applicationContext)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "restore_keys").setMethodCallHandler { call, result ->
scope.launch {
try {
when (call.method) {
"create" -> result.success(keys.create(call.arguments as String))
"get" -> result.success(keys.get(call.arguments as String))
"clear" -> { keys.clear(); result.success(null) }
else -> result.notImplemented()
}
} catch (e: Exception) {
result.error("restore_key", e.message, null)
}
}
}
}
}
The Dart side:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
const _channel = MethodChannel('restore_keys');
// Restore keys exist on Android only (and never on the web).
bool get _android => !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
Future<String?> createRestoreKey(Map<String, dynamic> options) async =>
_android ? _channel.invokeMethod<String>('create', jsonEncode(options)) : null;
Future<String?> getRestoreKey(Map<String, dynamic> request) async =>
_android ? _channel.invokeMethod<String>('get', jsonEncode(request)) : null;
Future<void> clearRestoreKey() async {
if (_android) await _channel.invokeMethod<void>('clear');
}
Call createRestoreKey after every sign-in with your server's creation options and send the result back to your server. On first launch, when no one is signed in, call getRestoreKey with your server's request options; if it returns a response, your server verifies it and signs the user in. Call clearRestoreKey on sign-out.
Create a key after every sign-in, and once at launch for a signed-in user who has none yet, such as someone who signed in before your update. If your app has a BackupAgent, also retrieve the key in onRestoreFinished, as Google's guide recommends.
3. The server
Your server needs four routes: creation options and verification when the key is made, and request options and verification when it is used. The options use the WebAuthn JSON formats PublicKeyCredentialCreationOptionsJSON and PublicKeyCredentialRequestOptionsJSON. The user.id must be a valid WebAuthn user ID or creating the key fails. Any WebAuthn server library can check the results, with two settings for restore keys: do not require user verification, because a restore runs without the user, and store them apart from passkeys so they never show up in a user's passkey list. When the restore response checks out, start a session for that user as your sign-in normally does. A Firebase Authentication app can mint a custom token with the Admin SDK and sign in with it.
Play Restore Keys checks your package name, signing fingerprints and domain for free and lists the server routes to build. It has 10 free checks a day and needs no account. We are also considering a hosted restore-key server for Firebase Auth apps. Nothing is for sale; you can join the waitlist on the Play Restore Keys page.
4. Test it
Android Studio can back up an app and restore it onto an emulator. Follow Google's testing page with both a Device to Device and a Cloud backup. The test passes when the reinstalled app signs in by itself. Our step-by-step test guide covers what the emulator does not show.
Other frameworks: React Native and Expo · Capacitor and Ionic · Cordova