How to fix identity_already_exists: Identity already linked to a user in Supabase

SupabaseINTERMEDIATEMEDIUMUpdated Dec 22, 2025· 241 views

This error occurs when attempting to link an OAuth identity that is already associated with another user account in Supabase Auth, commonly when converting anonymous users to verified accounts.

Quick fix

Fixes identity_already_exists: Identity already linked to a user

// Listen for the deep link or redirect
useEffect(() => {
  const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
    console.log('Auth event:', event);
  });

  // For mobile apps, listen to deep link
  const url = Linking.getInitialURL();
  url.then((deepLink) => {
    if (deepLink?.includes('error=identity_already_exists')) {
      console.log('Identity already linked to another user');
      // Handle error appropriately
    }
  });
}, []);
  1. Verify the error by checking the redirect URL
  2. Check if manual linking is enabled in your project
  3. Implement proper error handling for identity conflicts
  4. Query existing identities before linking
supabase — error
// Listen for the deep link or redirect
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event);
});
// For mobile apps, listen to deep link
const url = Linking.getInitialURL();
url.then((deepLink) => {
if (deepLink?.includes('error=identity_already_exists')) {
console.log('Identity already linked to another user');
// Handle error appropriately
}
});
}, []);
identity_already_exists: Identity already linked to a user

What this error means

The "identity_already_exists" error is thrown by Supabase Auth when you call `linkIdentity()` to associate an OAuth provider identity (like Google, GitHub, or Facebook) with a user account, but that specific identity is already linked to a different user in your database. This error is part of Supabase's identity linking system, which allows you to connect multiple authentication methods to a single user account. However, each unique identity (defined by the provider and the provider's user ID) can only be associated with one user account at a time. When Supabase detects that you're trying to link an identity that's already in use, it prevents the operation to maintain data integrity. A common scenario is when a user signs in with an OAuth provider, then your app automatically creates an anonymous session (perhaps after a reinstall), and you later attempt to link the same OAuth identity to the anonymous account. Since that OAuth identity is already tied to the original verified account, the link operation fails with this error.

Typical symptoms

  • linkIdentity() call fails when attempting to link an OAuth provider
  • Error message "Identity is already linked to another user" appears in redirect URL
  • Anonymous user conversion to verified account fails silently
  • No direct exception thrown in code, but redirect URL contains error parameter
  • Identity linking works for some users but fails for others using the same provider

Common causes

  1. The OAuth identity (provider + provider user ID) is already associated with a different user account
  2. Attempting to link an anonymous account to an OAuth identity that was previously used to create a verified account
  3. User reinstalled the app, creating a new anonymous session, then tried to sign in with the same OAuth provider
  4. Multiple user accounts were created with the same OAuth provider before enabling manual identity linking
  5. Identity was linked to a user account that was later deleted but identity record persists

How to fix "identity_already_exists: Identity already linked to a user"

1Verify the error by checking the redirect URL

After calling linkIdentity(), Supabase redirects to your configured redirect URL with error information. Check the URL parameters for the error:

typescript
// Listen for the deep link or redirect
useEffect(() => {
  const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
    console.log('Auth event:', event);
  });

  // For mobile apps, listen to deep link
  const url = Linking.getInitialURL();
  url.then((deepLink) => {
    if (deepLink?.includes('error=identity_already_exists')) {
      console.log('Identity already linked to another user');
      // Handle error appropriately
    }
  });
}, []);

For web apps, check the URL after redirect:

javascript
// Parse URL parameters after redirect
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const errorDescription = params.get('error_description');

if (error === 'identity_already_exists') {
  console.log('Error:', errorDescription);
  // Show user-friendly message
}
2Check if manual linking is enabled in your project

The linkIdentity() method requires the "Enable Manual Linking" option to be enabled in your Supabase project settings:

1. Go to your Supabase Dashboard
2. Navigate to Authentication → Settings
3. Scroll to "Auth Providers" section
4. Ensure "Enable Manual Linking" is toggled ON

If this setting is disabled, identity linking operations will fail regardless of whether the identity already exists.

3Implement proper error handling for identity conflicts

When the identity is already linked to another account, give users the option to sign in with that existing account instead:

typescript
async function handleIdentityLink(provider: 'google' | 'github' | 'facebook') {
  try {
    const { data, error } = await supabase.auth.linkIdentity({
      provider: provider,
    });

    if (error) {
      throw error;
    }

    // Success - identity linked
    console.log('Identity linked successfully');
  } catch (error: any) {
    // Error is typically returned via redirect URL, not thrown
    // Check redirect URL in your auth callback handler
  }
}

// In your auth callback handler (after redirect)
function handleAuthCallback() {
  const params = new URLSearchParams(window.location.search);
  const error = params.get('error');

  if (error === 'identity_already_exists') {
    // Show user a message with options
    showErrorDialog({
      title: 'Account Already Exists',
      message: 'This account is already linked to another user. Would you like to sign in with that account instead?',
      actions: [
        {
          label: 'Sign In',
          onClick: () => signInWithProvider(provider)
        },
        {
          label: 'Use Different Account',
          onClick: () => resetAndRetry()
        }
      ]
    });
  }
}
4Query existing identities before linking

To prevent the error proactively, check if a user already has an identity linked before attempting to add a new one:

typescript
async function checkExistingIdentities() {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    throw new Error('No user signed in');
  }

  // Get all identities for current user
  const identities = user.identities || [];

  console.log('Existing identities:', identities.map(i => ({
    provider: i.provider,
    id: i.id,
    created_at: i.created_at
  })));

  // Check if specific provider is already linked
  const hasGoogleIdentity = identities.some(i => i.provider === 'google');

  if (hasGoogleIdentity) {
    console.log('Google identity already linked');
    return false; // Don't attempt to link
  }

  return true; // Safe to link
}

// Use before calling linkIdentity
async function safeLinkIdentity(provider: string) {
  const canLink = await checkExistingIdentities();

  if (!canLink) {
    console.log('Identity already exists for this provider');
    return;
  }

  // Proceed with linking
  const { error } = await supabase.auth.linkIdentity({ provider });
  // Handle result
}
5Handle anonymous user conversion correctly

If you're converting anonymous users to verified accounts, implement a strategy to handle cases where the OAuth identity already exists:

typescript
async function convertAnonymousToVerified(provider: 'google' | 'github') {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    throw new Error('No user session');
  }

  // Check if user is anonymous
  const isAnonymous = user.is_anonymous ||
    (user.identities && user.identities.length === 0);

  if (!isAnonymous) {
    console.log('User is already verified');
    return;
  }

  // Attempt to link identity
  const { data, error } = await supabase.auth.linkIdentity({ provider });

  // Since error comes via redirect, set up listener
  // In your redirect handler:
  const handleRedirect = () => {
    const params = new URLSearchParams(window.location.search);

    if (params.get('error') === 'identity_already_exists') {
      // Identity exists on another account
      // Option 1: Sign user out and redirect to sign in
      await supabase.auth.signOut();
      await supabase.auth.signInWithOAuth({ provider });

      // Option 2: Transfer anonymous user's data to existing account
      // (requires custom server-side logic)
    }
  };
}

Advanced notes

Error Handling Quirk: Unlike most Supabase Auth errors, identity_already_exists is not thrown as a direct exception when calling linkIdentity(). Instead, Supabase redirects your application to the configured redirect URL with the error information embedded in the URL query parameters. This means you must implement redirect URL parsing and deep link handling to properly catch and display this error to users.

Database-Level Constraints: The uniqueness of identities is enforced at the database level in Supabase's auth.identities table. Each combination of provider and provider_id must be unique across all users. This constraint prevents identity hijacking and ensures data integrity.

Merging Accounts: If you need to merge two accounts (for example, transferring data from an anonymous account to an existing verified account), you'll need to implement custom server-side logic. Supabase doesn't provide a built-in account merging feature. You would typically:
1. Query both user accounts
2. Transfer data (user-generated content, settings, etc.) from the anonymous account to the verified account
3. Delete the anonymous account
4. Sign the user in to the verified account

Rate Limiting: Be aware that OAuth providers like Google and GitHub have rate limits on authentication requests. If users repeatedly attempt to link identities and encounter errors, you may hit these limits. Implement exponential backoff or cooldown periods for retry attempts.

Testing Identity Linking: In development, you may encounter this error frequently when testing with the same OAuth account. To reset, you can manually delete identity records from your Supabase dashboard (Authentication → Users → select user → Identities tab) or use the Supabase Management API to programmatically remove identities during testing.

Frequently asked questions

What does "identity_already_exists: Identity already linked to a user" mean?
The "identity_already_exists" error is thrown by Supabase Auth when you call linkIdentity() to associate an OAuth provider identity (like Google, GitHub, or Facebook) with a user account, but that specific identity is already linked to a different user in your database. This error is part of Supabase's identity linking system, which allows you to connect multiple authentication methods to a single user account. However, each unique identity (defined by the provider and the provider's user ID) can only be associated with one user account at a time.
How do I fix "identity_already_exists: Identity already linked to a user"?
This error occurs when attempting to link an OAuth identity that is already associated with another user account in Supabase Auth, commonly when converting anonymous users to verified accounts.
What causes "identity_already_exists: Identity already linked to a user"?
The OAuth identity (provider + provider user ID) is already associated with a different user account Attempting to link an anonymous account to an OAuth identity that was previously used to create a verified account User reinstalled the app, creating a new anonymous session, then tried to sign in with the same OAuth provider
Is the "identity_already_exists: Identity already linked to a user" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading