v139 · Web APIs · WebAuthn

Passkey Manager

Register passkeys, then test all four mediation modes — immediate (new in Chrome 139), conditional, required, and the register flow — and see exactly when each one shows UI and what it returns.

checking WebAuthn…
checking conditional mediation…
checking immediate mediation…
register passkey

Calls navigator.credentials.create() with a generated user handle and challenge. Stores the credential ID in this page's in-memory list.

Not triggered yet.
sign in (immediate) NEW

Uses mediation: 'immediate' — shows picker right away if credentials exist; rejects with NotAllowedError if none. No waiting, no pending promise.

Not triggered yet.
sign in (conditional)

Uses mediation: 'conditional' — populates autofill suggestions silently. Promise stays pending until the user picks from the list.

Not triggered yet.
sign in (required)

Uses mediation: 'required' — always shows the credential picker modal, even if no credentials are stored.

Not triggered yet.
registered passkeys (this session) 0
No passkeys registered yet. Click "Register passkey" above.

mediation mode comparison

mode shows UI when no credentials user gesture needed use case
"immediate" ← new Credentials exist for this origin Rejects: NotAllowedError Yes Sign-in page: show picker or fall through to form
"conditional" User focuses an input with autocomplete="webauthn" Promise stays pending No (passive) Autofill-style: populate suggestions alongside passwords
"required" Always (modal) Shows empty picker Yes Explicit "Use a passkey" button
"optional" May show picker; resolves silently if possible Resolves with null Depends Silent re-auth for low-stakes actions

code for each mode

// Register
const credential = await navigator.credentials.create({
  publicKey: {
    challenge: crypto.getRandomValues(new Uint8Array(32)),
    rp: { name: 'Demo App', id: location.hostname },
    user: { id: new Uint8Array(16), name: 'user@example.com', displayName: 'Demo User' },
    pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
    authenticatorSelection: { residentKey: 'required' },
  },
});

// Immediate (new in Chrome 139)
try {
  const cred = await navigator.credentials.get({
    mediation: 'immediate',
    publicKey: { challenge, rpId: location.hostname, allowCredentials: [] },
  });
  signInWith(cred);
} catch (err) {
  if (err.name === 'NotAllowedError') showSignInForm();
}

// Conditional — passive, no user gesture required
await navigator.credentials.get({
  mediation: 'conditional',
  publicKey: { challenge, rpId: location.hostname, allowCredentials: [] },
});

// Required — always shows picker
await navigator.credentials.get({
  mediation: 'required',
  publicKey: { challenge, rpId: location.hostname, allowCredentials: [] },
});

see also