Play Restore Keys · Guides
Google Play restore keys in Cordova 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 Cordova app needs a small native bridge. When we searched npm on 10 October 2026 we found no Cordova plugin for restore keys.
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. Bridge it to JavaScript
A small Cordova plugin with Kotlin sources. Your app's config.xml needs the Kotlin preference shown at the end. androidx.credentials needs compileSdk 35, so use cordova-android 14 or later.
<!-- plugin.xml, inside <platform name="android"> -->
<config-file target="res/xml/config.xml" parent="/*">
<feature name="RestoreKeys">
<param name="android-package" value="com.example.restorekeys.RestoreKeys" />
</feature>
</config-file>
<source-file src="src/android/RestoreKeyClient.kt" target-dir="app/src/main/java/com/example/restorekeys" />
<source-file src="src/android/RestoreKeys.kt" target-dir="app/src/main/java/com/example/restorekeys" />
<framework src="androidx.credentials:credentials:1.5.0" />
<framework src="androidx.credentials:credentials-play-services-auth:1.5.0" />
<framework src="org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1" />
<!-- the app's config.xml: compile Kotlin plugin sources -->
<preference name="GradlePluginKotlinEnabled" value="true" />
package com.example.restorekeys
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.apache.cordova.CallbackContext
import org.apache.cordova.CordovaPlugin
import org.json.JSONArray
class RestoreKeys : CordovaPlugin() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val keys by lazy { RestoreKeyClient(cordova.activity.applicationContext) }
override fun execute(action: String, args: JSONArray, callbackContext: CallbackContext): Boolean {
if (action !in setOf("create", "get", "clear")) return false
scope.launch {
try {
when (action) {
"create" -> callbackContext.success(keys.create(args.getString(0)))
"get" -> keys.get(args.getString(0))?.let { callbackContext.success(it) } ?: callbackContext.success()
"clear" -> { keys.clear(); callbackContext.success() }
}
} catch (e: Exception) {
callbackContext.error(e.message ?: e.toString())
}
}
return true
}
}
The JavaScript side:
// www/restore-keys.js (a <js-module> clobbering window.RestoreKeys); declare it outside <platform> so iOS gets it too
const call = (action, args) => cordova.platformId !== "android"
? Promise.resolve(null) // restore keys exist on Android only
: new Promise((resolve, reject) => cordova.exec((r) => resolve(r || null), reject, "RestoreKeys", action, args));
module.exports = {
create: (json) => call("create", [json]),
get: (json) => call("get", [json]),
clear: () => call("clear", []),
};
Apache Cordova has an open request for this API (cordova-android issue 2027). Until it ships, a plugin like this one is the way in.
The calls, in order:
// After your normal sign-in succeeds (your server's routes; the names are examples)
const options = await api.post("/restore-key/register/options");
const registration = await RestoreKeys.create(JSON.stringify(options));
if (registration) await api.post("/restore-key/register", JSON.parse(registration)); // null on iOS
// On first launch, when no one is signed in
const request = await api.post("/restore-key/login/options");
const assertion = await RestoreKeys.get(JSON.stringify(request));
if (assertion) await api.post("/restore-key/login", JSON.parse(assertion));
// On sign-out
await RestoreKeys.clear();
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 · Flutter