> You are reading Better Auth documentation for `v1.8 (Beta)`. This is not the current stable release. APIs may differ from the latest stable version.

# One Tap (/docs/beta/plugins/one-tap)

One Tap plugin for Better Auth



The One Tap plugin allows users to log in with a single tap using Google's One Tap API. The plugin
provides a simple way to integrate One Tap into your application, handling the client-side and server-side logic for you.

## Installation [#installation]

<Steps>
  <Step>
    ### Add the Server Plugin [#add-the-server-plugin]

    Add the One Tap plugin to your auth configuration:

    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { oneTap } from "better-auth/plugins"; // [!code highlight]

    export const auth = betterAuth({
        plugins: [
            oneTap({
              clientId: process.env.GOOGLE_CLIENT_ID as string, // [!code highlight]
            }),
        ]
    });
    ```
  </Step>

  <Step>
    ### Add the Client Plugin [#add-the-client-plugin]

    Add the client plugin and specify where the user should be redirected after sign-in or if additional verification (like 2FA) is needed.

    ```ts
    import { createAuthClient } from "better-auth/client";
    import { oneTapClient } from "better-auth/client/plugins"; // [!code highlight]

    export const authClient = createAuthClient({
      plugins: [
        oneTapClient({
          clientId: "YOUR_CLIENT_ID",
          // Optional client configuration:
          autoSelect: false,
          cancelOnTapOutside: true,
          context: "signin",
          additionalOptions: {
            // Any extra options for the Google initialize method
          },
          // Configure prompt behavior and exponential backoff:
          promptOptions: {
            baseDelay: 1000,   // Base delay in ms (default: 1000)
            maxAttempts: 5     // Maximum number of attempts before triggering onPromptNotification (default: 5)
          }
        })
      ]
    });
    ```
  </Step>
</Steps>

### Usage [#usage]

#### Prompt Mode (Default) [#prompt-mode-default]

To display the One Tap popup, simply call the oneTap method on your auth client:

```ts
import { authClient } from "@/lib/auth-client";

await authClient.oneTap(); // [!code highlight]
```

#### Button Mode [#button-mode]

To render a "Sign In with Google" button instead of showing the automatic prompt, use the `button` option:

<Tabs items="[&#x22;Vanilla JS&#x22;, &#x22;React Ref&#x22;]">
  <Tab value="Vanilla JS">
    ```tsx
    // In your component
    <div id="google-signin-button"></div>

    // Call oneTap with button configuration
    await authClient.oneTap({
      button: {
        container: "#google-signin-button", // CSS selector or HTMLElement
        config: {
          theme: "outline",
          size: "large",
          type: "standard",
          text: "signin_with"
        }
      }
    });
    ```
  </Tab>

  <Tab value="React Ref">
    ```tsx
    import { useEffect, useRef } from "react";

    function SignInButton() {
      const buttonRef = useRef<HTMLDivElement>(null);

      useEffect(() => {
        if (buttonRef.current) {
          authClient.oneTap({
            button: {
              container: buttonRef.current,
              config: {
                theme: "filled_blue",
                size: "large"
              }
            }
          });
        }
      }, []);

      return <div ref={buttonRef}></div>;
    }
    ```
  </Tab>
</Tabs>

##### FedCM Account Chooser [#fedcm-account-chooser]

By default, clicking the button opens Google's sign-in popup window. Set `fedCM: true` to open the browser's own account chooser over your page instead, with no new window. This is Google's [FedCM button flow](https://developers.google.com/identity/gsi/web/guides/fedcm-migration):

```ts
await authClient.oneTap({
  button: {
    container: "#google-signin-button",
    fedCM: true, // [!code highlight]
  },
});
```

With `autoSelect: true`, a returning user who is signed in to Google is signed in without seeing the chooser.

The FedCM button flow runs on Chrome 125+ on desktop and Chrome 128+ on Android. Other browsers open the popup window, so you don't need to detect support yourself.

### Customizing Redirect Behavior [#customizing-redirect-behavior]

By default, after a successful login the plugin will hard redirect the user to `/`. You can customize this behavior as follows:

#### Avoiding a Hard Redirect [#avoiding-a-hard-redirect]

Pass fetchOptions with an onSuccess callback to handle the login response without a page reload:

```ts
import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  fetchOptions: {
    onSuccess: () => {
      // For example, use a router to navigate without a full reload:
      router.push("/dashboard");
    }
  }
});
```

#### Specifying a Custom Callback URL [#specifying-a-custom-callback-url]

To perform a hard redirect to a different page after login, use the callbackURL option:

```ts
import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  callbackURL: "/dashboard"
});
```

#### Handling Prompt Dismissals with Exponential Backoff [#handling-prompt-dismissals-with-exponential-backoff]

If the prompt is dismissed without a sign-in, the plugin will retry showing the One Tap prompt using exponential backoff based on your configured promptOptions. A prompt your app closes with `google.accounts.id.cancel()` is not retried.

A skipped prompt is not retried. Google doesn't report why the prompt was skipped, and after a user closes it, Google holds the prompt back for a cooldown period that retries can't shorten.

The plugin passes a listener to Google's `prompt()` to track these moments, so Google Identity Services logs a FedCM migration warning about prompt UI status methods. Google logs it for any listener, even one that calls none of those methods. Under FedCM, the plugin only calls the methods that FedCM supports, so you can ignore the warning.

When the prompt is skipped, or the maximum number of attempts is reached without a successful sign-in, you can use the onPromptNotification callback to be notified—allowing you to render an alternative UI (e.g., a traditional Google Sign-In button) so users can restart the process manually:

```ts
import { authClient } from "@/lib/auth-client";

await authClient.oneTap({
  onPromptNotification: (notification) => {
    console.warn("Prompt was dismissed or skipped. Consider displaying an alternative sign-in option.", notification);
    // Render your alternative UI here
  }
});
```

### Client Options [#client-options]

* `clientId`: The client ID for your Google One Tap API.
* `autoSelect`: Automatically select the account if the user is already signed in. Default is false.
* `cancelOnTapOutside`: Cancel the One Tap popup when the user taps outside it. Note that this option may not have an effect when FedCM is active, as the browser manages the dismiss behavior. Default is true.
* `uxMode`: The mode to use for the Google One Tap flow. Can be "popup" or "redirect". Default is "popup".
* `context`: The context in which the One Tap API should be used. Can be "signin", "signup", or "use". Default is "signin".
* `additionalOptions`: Extra options to pass to Google's initialize method as per the [Google Identity Services docs](https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt).
* `promptOptions`: Configuration for the prompt behavior and exponential backoff:
  * `baseDelay`: Base delay in milliseconds for retries. Default is 1000.
  * `maxAttempts`: Maximum number of prompt attempts before invoking the `onPromptNotification` callback. Default is 5.
  * `fedCM`: When enabled, calls [`navigator.credentials.preventSilentAccess()`](https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/preventSilentAccess) on sign-out to clear the browser's FedCM credential state. FedCM itself is managed by the Google Identity Services library and cannot be disabled. Default is true.

### Button Mode Options [#button-mode-options]

When using button mode, pass a `button` object to the `oneTap` action with the following properties:

* `container`: The HTML element or CSS selector where the button should be rendered (required)
* `config`: Button configuration options (optional):
  * `type`: Button type - `"standard"` (default) or `"icon"` (icon-only)
  * `theme`: Button theme - `"outline"` (default), `"filled_blue"`, or `"filled_black"`
  * `size`: Button size - `"large"` (default), `"medium"`, or `"small"`
  * `text`: Button text - `"signin_with"` (default), `"signup_with"`, `"continue_with"`, or `"signin"`
  * `shape`: Button shape - `"rectangular"` (default), `"pill"`, `"circle"`, or `"square"`
  * `logo_alignment`: Google logo alignment - `"left"` (default) or `"center"` (for standard button only)
  * `width`: Minimum button width in pixels (max 400)
  * `locale`: Display button using specified language code (e.g., `"zh_CN"`)
* `fedCM`: Open the browser's FedCM account chooser instead of Google's popup window when the button is clicked. See [FedCM Account Chooser](#fedcm-account-chooser). Default is false.

### Server Options [#server-options]

* `disableSignUp`: Disable the sign-up option, allowing only existing users to sign in. Default is false.
* `clientId`: Google client ID for ID token audience verification. Required unless `socialProviders.google.clientId` is configured.

<Callout type="info">
  **Restricting to a Google Workspace domain (`hd`)**

  The One Tap callback applies the hosted-domain restriction from
  `socialProviders.google.hd`, matching the standard Google sign-in flow:
  when `hd` is set to a domain, a token whose `hd` claim is missing or does
  not match is rejected. Set `hd: "*"` to allow any Google Workspace hosted
  domain while still rejecting tokens with no `hd` claim.

  This value is read from `socialProviders.google`, so the restriction only
  applies when you configure the Google provider. If you set up One Tap with
  only `oneTap({ clientId })` and no `socialProviders.google`, no
  hosted-domain restriction is applied. To restrict One Tap sign-in to a
  Workspace domain, configure `socialProviders.google` with `hd`.
</Callout>

<Callout type="info">
  **Mapping the Google profile to your user**

  When `socialProviders.google` is configured, One Tap builds the user with its `mapProfileToUser` or `getUserInfo`, as the standard Google sign-in does. An extra field you map, such as a first name from `given_name`, is saved only if it is declared in `user.additionalFields` and not marked `input: false`. The account is always keyed by the verified token's `sub`.

  With `overrideUserInfoOnSignIn`, a returning user is updated from the mapped profile on every One Tap sign-in.

  With only `oneTap({ clientId })` and no `socialProviders.google`, One Tap uses the token's `name`, `email`, `email_verified`, and `picture` claims.
</Callout>

### Authorized JavaScript origins [#authorized-javascript-origins]

Ensure you have configured the Authorized JavaScript origins (e.g., [http://localhost:3000](http://localhost:3000), [https://example.com](https://example.com)) for your Client ID in the Google Cloud Console. This is a required step for the Google One Tap API, and it will not function correctly unless your origins are correctly set.

