# Linking identities

> How Supabase Auth links several sign-in methods to one user on SupaHost: automatic linking by verified email, manual linking with linkIdentity, listing and unlinking identities.

Source: https://supahost.app/docs/auth/identity-linking · Updated 2026-11-11

One person can sign in to your app in several ways: a password today, Google next week, GitHub from their work laptop. Each way is an **identity**, stored as a row in `auth.identities`, and all of them can point at the same row in `auth.users`. Linking keeps them as one user, with one user id, so their data follows them whichever button they press.

There are two kinds of linking.

## Automatic linking

When someone signs in with a provider and the provider vouches for an email address that already belongs to a user, Auth adds the new identity to that existing user instead of creating a second one.

For example, Jo signs up with `jo@example.com` and a password, and confirms the address. Later Jo clicks "Continue with Google" with the same Gmail address. Auth sees a verified email that matches, links the Google identity to Jo's existing user, and Jo lands in the same account with the same data.

This only happens with **verified** emails. If either side's email isn't verified (an unconfirmed sign-up, or a provider that doesn't confirm emails), Auth doesn't link, because doing so would let anyone who registers an address they don't own take over the real owner's account. That's also why turning on "Skip email confirmation" in production weakens this protection: addresses are then treated as confirmed without proof.

Automatic linking is always on. There's no setting for it.

## Manual linking

Manual linking lets a signed-in user attach another provider to their account, even if its email is different (a personal Gmail on a work account, say). Turn it on in *Dashboard → your project → **Auth** tab → **Sign-in & security** → **Manual identity linking*** and click **Save sign-in settings**.

Then, from a settings page where the user is signed in:

```ts
const { data, error } = await supabase.auth.linkIdentity({
  provider: 'github',
  options: { redirectTo: 'https://chat.example.com/settings/accounts' },
})
```

This works like an OAuth sign-in: the user goes to GitHub, approves, and comes back to `redirectTo` (which must be in your Redirect URLs). Instead of signing in as someone new, the GitHub identity is added to the current user. The provider must be enabled in the **OAuth providers** card.

If manual linking is off, the call fails with the error code `manual_linking_disabled`. If the GitHub account is already linked to a different user, it fails with `identity_already_exists`.

## Listing a user's identities

```ts
const { data, error } = await supabase.auth.getUserIdentities()

for (const identity of data!.identities) {
  console.log(identity.provider, identity.identity_data?.email, identity.created_at)
}
```

A settings page for a team chat app might show a row per provider, with "Connect" for the missing ones (calling `linkIdentity`) and "Disconnect" for the linked ones.

## Unlinking

```ts
const { data } = await supabase.auth.getUserIdentities()
const github = data!.identities.find((i) => i.provider === 'github')

if (github) {
  const { error } = await supabase.auth.unlinkIdentity(github)
}
```

A user must keep at least one identity. Unlinking the last one fails with `single_identity_not_deletable`. Before unlinking, make sure the user has another way to sign in that they can actually use, such as a password they know.

Unlinking removes the identity, not the user, and it doesn't change the user's email. If the unlinked provider's email was the user's primary address, update it with `updateUser({ email })` if needed.

## Linking from an anonymous user

[Anonymous users](/docs/auth/anonymous) become permanent by linking. Adding an email with `updateUser({ email })` doesn't need manual linking. Adding an OAuth provider with `linkIdentity` does, so turn on **Manual identity linking** if your guests should be able to "Continue with Google" to keep their data.

## Where identities show up

- `user.identities` on the user object, returned by `getUser()`.
- `app_metadata.providers` in the user's JWT, a list such as `["email", "google"]`, which you can read in policies with `auth.jwt()`.
- The `auth.identities` table, which you can query in Studio's SQL editor:

```sql
select u.email, i.provider, i.created_at
from auth.identities i
join auth.users u on u.id = i.user_id
order by u.email, i.created_at;
```

## Linking users who already have two accounts

If someone ended up with two separate users (for example a Google sign-in before email linking applied, with an unverified email), Auth won't merge them. Merging is your job: pick the user to keep, move rows from the other user's id to it with the `service_role` key, then delete the extra user with the admin API. See [Managing users](/docs/auth/user-management).

## Related

- [Social login (OAuth)](/docs/auth/social-login)
- [Sign in with Google](/docs/auth/google)
- [Google sign-in fails for some users](/kb/google-auth-fails)
- The [linkIdentity reference](https://supabase.com/docs/reference/javascript/auth-linkidentity)
