How to fix identity_already_exists: Identity already linked to a user in Supabase
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
}
});
}, []);- Verify the error by checking the redirect URL
- Check if manual linking is enabled in your project
- Implement proper error handling for identity conflicts
- Query existing identities before linking
// Listen for the deep link or redirectuseEffect(() => {const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {console.log('Auth event:', event);});// For mobile apps, listen to deep linkconst 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
- 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
- Multiple user accounts were created with the same OAuth provider before enabling manual identity linking
- 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"
After calling linkIdentity(), Supabase redirects to your configured redirect URL with error information. Check the URL parameters for the 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
}
});
}, []);For web apps, check the URL after redirect:
// 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
}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.
When the identity is already linked to another account, give users the option to sign in with that existing account instead:
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()
}
]
});
}
}To prevent the error proactively, check if a user already has an identity linked before attempting to add a new one:
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
}If you're converting anonymous users to verified accounts, implement a strategy to handle cases where the OAuth identity already exists:
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
- 📚Supabase Official Documentation
- 📖supabase.com - auth identity linking(Official Docs)
- 🐙github.com - 1762(GitHub)
- 🐙github.com - 27061(GitHub)
- 📖supabase.com - error codes(Official Docs)
- 📖supabase.com - auth linkidentity(Official Docs)