How to fix mfa_factor_name_conflict: MFA factor name is already in use in Supabase

SupabaseINTERMEDIATEHIGHUpdated Dec 29, 2025· 159 views

The mfa_factor_name_conflict error occurs when you try to enroll a new MFA factor with a friendly name that's already in use by another factor for the same user. This happens because Supabase enforces unique friendly names to help users distinguish between multiple authentication methods.

Quick fix

Fixes mfa_factor_name_conflict

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);

// Get all factors for the current user
const { data: factors, error } = await supabase.auth.mfa.listFactors();

if (error) {
  console.error('Error listing factors:', error);
} else {
  console.log('All factors:');
  factors.all.forEach(factor => {
    console.log(
      `- ${factor.friendly_name} (${factor.factor_type}, Status: ${factor.status})`
    );
  });
}
  1. List all MFA factors for the current user
  2. Remove unverified MFA factors before enrolling
  3. Use unique and descriptive friendly names
  4. Implement proper MFA flow with error handling
supabase — error
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
// Get all factors for the current user
const { data: factors, error } = await supabase.auth.mfa.listFactors();
if (error) {
console.error('Error listing factors:', error);
} else {
console.log('All factors:');
factors.all.forEach(factor => {
console.log(
`- ${factor.friendly_name} (${factor.factor_type}, Status: ${factor.status})`
);
});
}
mfa_factor_name_conflict: MFA factor name is already in use

What this error means

The "mfa_factor_name_conflict" error indicates that you're attempting to create a new MFA factor (such as TOTP or phone authentication) with a friendly name that already exists for the current user. Supabase uses friendly names to help users identify which authentication method they're using. Each user can have up to 10 different MFA factors, but each factor must have a unique friendly name within that user's account. This prevents confusion when a user has multiple TOTP authenticators, phone numbers, or other MFA methods enrolled. This error commonly occurs when: - A user tries to set up a second TOTP authenticator with the same name - The application defaults friendly names to the user's email or username instead of unique identifiers - A previous MFA enrollment was cancelled but the factor name wasn't cleaned up - Unverified factors with duplicate names accumulate on the account

Typical symptoms

  • MFA enrollment fails when attempting to enroll a new factor
  • Error message displays: 'mfa_factor_name_conflict: MFA factor name is already in use'
  • Users cannot add a second authentication method if using a generic name like email
  • Mobile app enrollment fails during the MFA setup flow
  • The same friendly name cannot be reused after deleting a factor

Common causes

  1. Using generic friendly names like email addresses or 'My Authenticator' for multiple factors
  2. Not removing unverified factors before creating new enrollment attempts
  3. Application logic that automatically assigns friendly names without checking uniqueness
  4. Accumulated unverified MFA factors from cancelled enrollment flows
  5. Not validating friendly names in your client code before sending enroll requests

How to fix "mfa_factor_name_conflict: MFA factor name is already in use"

1List all MFA factors for the current user

First, check what MFA factors are already enrolled, including unverified ones:

javascript
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);

// Get all factors for the current user
const { data: factors, error } = await supabase.auth.mfa.listFactors();

if (error) {
  console.error('Error listing factors:', error);
} else {
  console.log('All factors:');
  factors.all.forEach(factor => {
    console.log(
      `- ${factor.friendly_name} (${factor.factor_type}, Status: ${factor.status})`
    );
  });
}

This shows all TOTP, phone, and other factors, including unverified ones that may be causing the conflict.

2Remove unverified MFA factors before enrolling

Before creating a new factor enrollment, remove any existing unverified factors. This is the most important step:

javascript
import { createClient } from '@supabase/supabase-js';

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);

async function cleanupUnverifiedFactors() {
  // Get all factors
  const { data: factors, error } = await supabase.auth.mfa.listFactors();

  if (error) {
    console.error('Error listing factors:', error);
    return;
  }

  // Remove all unverified factors
  for (const factor of factors.all) {
    if (factor.status === 'unverified') {
      console.log(`Unenrolling unverified factor: ${factor.friendly_name}`);
      const { error: unenrollError } = await supabase.auth.mfa.unenrollFactor(
        factor.id
      );

      if (unenrollError) {
        console.error('Error unenrolling factor:', unenrollError);
      }
    }
  }
}

// Clean up before enrolling
await cleanupUnverifiedFactors();

// Now enroll new factor with unique name
const { data: newFactor, error: enrollError } =
  await supabase.auth.mfa.enrollFactor({
    factorType: 'totp',
    friendlyName: 'My Main Authenticator', // Must be unique
  });
3Use unique and descriptive friendly names

Instead of generic names like email addresses, use descriptive names that distinguish between factors:

javascript
// GOOD: Unique, descriptive names
const names = {
  firstTOTP: 'Authenticator App - iPhone',
  secondTOTP: 'Authenticator App - Work Computer',
  phone: 'SMS - +1 555-0123',
  backup: 'Recovery Codes',
};

// BAD: Generic or potentially duplicate names
const badNames = {
  auth: 'My Email', // Too generic, may conflict
  mfa: 'user@example.com', // Email might already be a factor
  backup: 'My Phone', // Vague
};

Ask the user for a unique name during MFA setup:

javascript
async function enrollMFAWithUserInput() {
  // Clean up first
  await cleanupUnverifiedFactors();

  // Prompt user for a unique name
  const friendlyName = prompt(
    'Give this authenticator a unique name (e.g., "iPhone", "Work Computer"):'
  );

  if (!friendlyName) {
    console.error('Friendly name is required');
    return;
  }

  // Validate it doesn't conflict with existing factors
  const { data: factors } = await supabase.auth.mfa.listFactors();
  const nameExists = factors.all.some(
    f => f.friendly_name.toLowerCase() === friendlyName.toLowerCase()
  );

  if (nameExists) {
    alert('That name is already in use. Please choose a different one.');
    return;
  }

  // Proceed with enrollment
  const { data: newFactor, error } = await supabase.auth.mfa.enrollFactor({
    factorType: 'totp',
    friendlyName,
  });

  if (error) {
    console.error('Enrollment failed:', error);
  } else {
    console.log('Factor enrolled successfully');
  }
}
4Implement proper MFA flow with error handling

Create a complete MFA enrollment flow that handles the mfa_factor_name_conflict error:

javascript
async function enrollMFAWithErrorHandling(friendlyName) {
  try {
    // Step 1: Clean up unverified factors
    const { data: factors } = await supabase.auth.mfa.listFactors();

    for (const factor of factors.all) {
      if (factor.status === 'unverified') {
        await supabase.auth.mfa.unenrollFactor(factor.id);
      }
    }

    // Step 2: Attempt to enroll with the provided name
    const { data: enrollData, error: enrollError } =
      await supabase.auth.mfa.enrollFactor({
        factorType: 'totp',
        friendlyName,
      });

    if (enrollError) {
      if (enrollError.message.includes('mfa_factor_name_conflict')) {
        throw new Error(
          `The name "${friendlyName}" is already in use. Please choose a different name.`
        );
      }
      throw enrollError;
    }

    // Step 3: Display QR code to user
    if (enrollData?.totp?.qr_code) {
      console.log('QR Code:', enrollData.totp.qr_code);
      // Display in UI for user to scan
    }

    // Step 4: Wait for user to verify
    return enrollData;
  } catch (error) {
    console.error('MFA enrollment failed:', error.message);
    // Show error message to user and let them retry with different name
    throw error;
  }
}

// Usage
try {
  await enrollMFAWithErrorHandling('iPhone Authenticator');
} catch (error) {
  console.error('Setup failed:', error.message);
  // Show retry UI to user
}
5Handle cancelled MFA setup properly

When users cancel MFA setup, unenroll the unverified factor:

javascript
async function cancelMFASetup() {
  // Get all unverified factors
  const { data: factors } = await supabase.auth.mfa.listFactors();

  // Unenroll any unverified factors (cleanup from cancelled flow)
  for (const factor of factors.all) {
    if (factor.status === 'unverified') {
      console.log(`Removing unverified factor: ${factor.friendly_name}`);
      await supabase.auth.mfa.unenrollFactor(factor.id);
    }
  }

  console.log('MFA setup cancelled and cleaned up');
}

// Wire this to cancel button
document.getElementById('cancel-mfa-btn').addEventListener('click', () => {
  cancelMFASetup();
});

This prevents unverified factors from accumulating and causing name conflicts on subsequent attempts.

6Use timestamps or UUIDs for automatic friendly names

If you must auto-generate friendly names, use unique identifiers:

javascript
import { v4 as uuidv4 } from 'uuid';

// Option 1: Use timestamp (less readable but unique)
function generateFriendlyName(type) {
  const now = new Date();
  const timestamp = now.toLocaleString();
  return `${type} - ${timestamp}`;
}

// Option 2: Use device info + timestamp (more readable)
function generateFriendlyNameWithDevice(type) {
  const ua = navigator.userAgent;
  const isChrome = /Chrome/.test(ua) ? 'Chrome' : '';
  const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua) ? 'Safari' : '';
  const browser = isChrome || isSafari || 'Unknown';
  return `${type} - ${browser} - ${Date.now()}`;
}

// Option 3: Use UUID (most unique, less readable)
function generateFriendlyNameWithUUID(type) {
  const shortId = uuidv4().split('-')[0];
  return `${type} (${shortId})`;
}

// Usage
const name = generateFriendlyNameWithDevice('TOTP');
console.log(name); // 'TOTP - Chrome - 1735454921000'

This ensures no name conflicts, though users won't be able to easily identify their factors.

7Verify the fix works

Test that MFA enrollment works without conflicts:

javascript
async function testMFAEnrollment() {
  // 1. List existing factors
  const { data: initialFactors } = await supabase.auth.mfa.listFactors();
  console.log('Initial factors:', initialFactors.all.length);

  // 2. Clean up unverified
  for (const factor of initialFactors.all) {
    if (factor.status === 'unverified') {
      await supabase.auth.mfa.unenrollFactor(factor.id);
    }
  }

  // 3. Enroll new factor
  const { data: newFactor, error } = await supabase.auth.mfa.enrollFactor({
    factorType: 'totp',
    friendlyName: 'Test Authenticator',
  });

  if (error) {
    console.error('Enrollment failed:', error);
  } else {
    console.log('Successfully enrolled:', newFactor.friendly_name);
  }

  // 4. Verify it appears in list
  const { data: finalFactors } = await supabase.auth.mfa.listFactors();
  const newFactorExists = finalFactors.all.some(
    f => f.friendly_name === 'Test Authenticator'
  );

  console.log('New factor visible:', newFactorExists);
}

testMFAEnrollment().catch(console.error);

Advanced notes

Understanding MFA Factor Lifecycle

When a user initiates MFA enrollment, Supabase creates an unverified factor. The factor remains unverified until the user completes verification (entering a TOTP code or confirming a phone number). If the user closes the browser or cancels the flow before completing verification, the unverified factor remains in the database.

Unverified factors count toward uniqueness checks, which is why they can cause name conflicts. This is intentional to prevent users from accumulating abandoned factors.

Best Practices for MFA Enrollment UI

1. Always clean up first: Remove all unverified factors before starting enrollment
2. Ask for unique names: Let users choose descriptive names like "iPhone" or "Work Laptop"
3. Validate before sending: Check if the name already exists before calling enrollFactor()
4. Handle cancellation: Unenroll unverified factors if the user cancels the flow
5. Show factor list: Display all existing factors so users know what names are taken

Supabase Auth v2 Changes

Recent updates to Supabase Auth improved automatic cleanup of unverified factors. However, relying only on automatic cleanup can be problematic if network issues prevent cleanup from executing. Always implement client-side cleanup as the primary defense.

Testing with Local Supabase

When testing MFA locally, use Supabase CLI to manage factors:

bash
# List factors for a user
supabase inspect user <user_id>

# Remove a factor directly (admin only)
supabase db --command "DELETE FROM auth.mfa_factors WHERE id = '<factor_id>'"

Rate Limiting

Supabase may rate-limit rapid factor enrollments. If you retry too quickly after failures, you might hit rate limits. Implement exponential backoff:

javascript
async function enrollWithRetry(friendlyName, maxRetries = 3) {
  let delay = 1000;

  for (let i = 0; i < maxRetries; i++) {
    try {
      return await supabase.auth.mfa.enrollFactor({
        factorType: 'totp',
        friendlyName,
      });
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      console.log(`Retry in ${delay}ms...`);
      await new Promise(r => setTimeout(r, delay));
      delay *= 2;
    }
  }
}

Frequently asked questions

What does "mfa_factor_name_conflict" mean?
The "mfa_factor_name_conflict" error indicates that you're attempting to create a new MFA factor (such as TOTP or phone authentication) with a friendly name that already exists for the current user. Supabase uses friendly names to help users identify which authentication method they're using. Each user can have up to 10 different MFA factors, but each factor must have a unique friendly name within that user's account.
How do I fix "mfa_factor_name_conflict"?
The mfa_factor_name_conflict error occurs when you try to enroll a new MFA factor with a friendly name that's already in use by another factor for the same user. This happens because Supabase enforces unique friendly names to help users distinguish between multiple authentication methods.
What causes "mfa_factor_name_conflict"?
Using generic friendly names like email addresses or 'My Authenticator' for multiple factors Not removing unverified factors before creating new enrollment attempts Application logic that automatically assigns friendly names without checking uniqueness
Is the "mfa_factor_name_conflict" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading