# How to Handle Anonymous Users in Firebase Auth

- Tool: Firebase
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Firebase (Spark and Blaze plans), firebase v9+ modular SDK
- Last updated: March 2026

## TL;DR

To handle anonymous users in Firebase, call signInAnonymously() to create a temporary account with a unique UID. The user can interact with your app immediately without providing credentials. When they are ready to create a permanent account, use linkWithCredential() or linkWithPopup() to upgrade the anonymous account while preserving their UID and all associated data. Always implement account linking to prevent data loss when anonymous users convert.

## Managing Anonymous Users in Firebase Auth

Anonymous authentication lets users try your app without creating an account upfront. Firebase assigns a real UID to anonymous users so they can save data, use Firestore, and interact with your app. When they decide to sign up, you link their anonymous account to a permanent provider, preserving their UID and all data. This tutorial covers the full lifecycle from anonymous sign-in to account conversion.

## Before you start

- A Firebase project with Authentication enabled
- Anonymous sign-in provider enabled in Firebase Console > Authentication > Sign-in method
- The firebase npm package installed (v9 or later)
- Basic knowledge of JavaScript/TypeScript

## Step-by-step guide

### 1. Sign in anonymously

Call signInAnonymously() to create a temporary user account. Firebase generates a unique UID for this anonymous user, and the onAuthStateChanged listener fires with the user object. The user can now read and write to Firestore, Storage, and other Firebase services using this UID. Anonymous sessions persist across page refreshes by default.

```
import { getAuth, signInAnonymously, onAuthStateChanged } from 'firebase/auth'

const auth = getAuth()

async function signInAsGuest() {
  try {
    const userCredential = await signInAnonymously(auth)
    const user = userCredential.user
    console.log('Anonymous UID:', user.uid)
    console.log('Is anonymous:', user.isAnonymous) // true
    return user
  } catch (error: any) {
    console.error('Anonymous sign-in failed:', error.code)
    throw error
  }
}
```

**Expected result:** A new anonymous user is created with a unique UID, and isAnonymous is true.

### 2. Detect whether the current user is anonymous

Use the isAnonymous property on the user object to determine if the current user signed in anonymously. This lets you show different UI states: anonymous users see a prompt to create an account, while permanent users see their profile. Check this property in your auth state listener or when rendering protected components.

```
import { getAuth, onAuthStateChanged } from 'firebase/auth'
import { useEffect, useState } from 'react'

function useAuthState() {
  const [user, setUser] = useState<any>(null)
  const [isAnonymous, setIsAnonymous] = useState(false)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const auth = getAuth()
    const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {
      setUser(firebaseUser)
      setIsAnonymous(firebaseUser?.isAnonymous ?? false)
      setLoading(false)
    })
    return () => unsubscribe()
  }, [])

  return { user, isAnonymous, loading }
}

// Usage in a component:
// const { user, isAnonymous } = useAuthState()
// if (isAnonymous) show 'Create account' banner
```

**Expected result:** The hook correctly identifies anonymous users and updates when they convert to permanent accounts.

### 3. Link the anonymous account to email/password

When an anonymous user decides to create an account, use linkWithCredential() to upgrade their anonymous account to a permanent email/password account. This preserves the same UID so all their existing Firestore data, Storage files, and other references remain valid. The user does not need to sign out and sign back in.

```
import { getAuth, EmailAuthProvider, linkWithCredential } from 'firebase/auth'

const auth = getAuth()

async function linkEmailPassword(email: string, password: string) {
  const user = auth.currentUser
  if (!user || !user.isAnonymous) {
    throw new Error('No anonymous user to link')
  }

  const credential = EmailAuthProvider.credential(email, password)

  try {
    const result = await linkWithCredential(user, credential)
    console.log('Account linked. UID preserved:', result.user.uid)
    console.log('Is anonymous:', result.user.isAnonymous) // false
    return result.user
  } catch (error: any) {
    if (error.code === 'auth/email-already-in-use') {
      console.error('This email is already registered. Sign in instead.')
    }
    throw error
  }
}
```

**Expected result:** The anonymous account is upgraded to email/password with the same UID. isAnonymous becomes false.

### 4. Link the anonymous account to a social provider

You can also link anonymous accounts to social providers like Google or GitHub using linkWithPopup() or linkWithRedirect(). The process is similar to linkWithCredential but uses the provider's OAuth flow. After linking, the user has a permanent account and can sign in with the social provider in the future.

```
import { getAuth, GoogleAuthProvider, linkWithPopup } from 'firebase/auth'

const auth = getAuth()

async function linkGoogleAccount() {
  const user = auth.currentUser
  if (!user || !user.isAnonymous) {
    throw new Error('No anonymous user to link')
  }

  const provider = new GoogleAuthProvider()

  try {
    const result = await linkWithPopup(user, provider)
    console.log('Google account linked. UID:', result.user.uid)
    return result.user
  } catch (error: any) {
    if (error.code === 'auth/credential-already-in-use') {
      console.error('This Google account is linked to another user.')
      // Consider merging data from anonymous to existing account
    }
    throw error
  }
}
```

**Expected result:** The Google account is linked and the anonymous user becomes a permanent Google-authenticated user.

### 5. Write security rules that work with anonymous users

Anonymous users have a valid request.auth object with a real UID. Your security rules can grant different levels of access based on whether the user is anonymous. Use request.auth.token.firebase.sign_in_provider to check the authentication method. Grant anonymous users read access but require a permanent account for sensitive writes.

```
// firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Public data: any authenticated user (including anonymous) can read
    match /products/{productId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
        && request.auth.token.firebase.sign_in_provider != 'anonymous';
    }

    // User data: scoped to their UID (works for both anonymous and permanent)
    match /carts/{userId}/{document=**} {
      allow read, write: if request.auth != null
        && request.auth.uid == userId;
    }

    // Orders: require a permanent account
    match /orders/{orderId} {
      allow create: if request.auth != null
        && request.auth.token.firebase.sign_in_provider != 'anonymous';
      allow read: if request.auth != null
        && request.auth.uid == resource.data.userId;
    }
  }
}
```

**Expected result:** Anonymous users can browse and save cart items but must create a permanent account to place orders.

### 6. Clean up abandoned anonymous accounts

Anonymous accounts that are never linked accumulate in your auth user list. With Firebase Identity Platform (upgrade from standard Auth), anonymous accounts auto-delete after 30 days of inactivity. Without Identity Platform, write a scheduled Cloud Function using the Admin SDK to list and delete anonymous users that have not been active for a specified period.

```
// Cloud Function to clean up old anonymous accounts
import { onSchedule } from 'firebase-functions/v2/scheduler'
import { getAuth } from 'firebase-admin/auth'
import { initializeApp } from 'firebase-admin/app'

initializeApp()

export const cleanupAnonymousUsers = onSchedule('every day 02:00', async () => {
  const auth = getAuth()
  const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000 // 30 days

  let nextPageToken: string | undefined
  do {
    const listResult = await auth.listUsers(1000, nextPageToken)
    const toDelete = listResult.users
      .filter(user => {
        const isAnon = user.providerData.length === 0
        const lastSignIn = new Date(user.metadata.lastSignInTime).getTime()
        return isAnon && lastSignIn < cutoff
      })
      .map(user => user.uid)

    if (toDelete.length > 0) {
      await auth.deleteUsers(toDelete)
      console.log(`Deleted ${toDelete.length} anonymous users`)
    }

    nextPageToken = listResult.pageToken
  } while (nextPageToken)
})
```

**Expected result:** Anonymous accounts older than 30 days are automatically deleted on a daily schedule.

## Complete code example

File: `anonymous-auth.ts`

```typescript
import {
  getAuth,
  signInAnonymously,
  onAuthStateChanged,
  EmailAuthProvider,
  GoogleAuthProvider,
  linkWithCredential,
  linkWithPopup,
  User,
} from 'firebase/auth'

const auth = getAuth()

export async function signInAsGuest(): Promise<User> {
  const { user } = await signInAnonymously(auth)
  return user
}

export function onAuthChange(
  callback: (user: User | null, isAnonymous: boolean) => void
): () => void {
  return onAuthStateChanged(auth, (user) => {
    callback(user, user?.isAnonymous ?? false)
  })
}

export async function upgradeToEmail(
  email: string,
  password: string
): Promise<User> {
  const user = auth.currentUser
  if (!user?.isAnonymous) throw new Error('Not an anonymous user')
  const credential = EmailAuthProvider.credential(email, password)
  const { user: linked } = await linkWithCredential(user, credential)
  return linked
}

export async function upgradeToGoogle(): Promise<User> {
  const user = auth.currentUser
  if (!user?.isAnonymous) throw new Error('Not an anonymous user')
  const provider = new GoogleAuthProvider()
  const { user: linked } = await linkWithPopup(user, provider)
  return linked
}

export function isGuest(): boolean {
  return auth.currentUser?.isAnonymous ?? false
}

export function getCurrentUid(): string | null {
  return auth.currentUser?.uid ?? null
}
```

## Common mistakes

- **Creating a new anonymous user on every page load instead of checking for an existing session** — undefined Fix: Always check onAuthStateChanged first. If a user (anonymous or permanent) already exists, do not call signInAnonymously again. Only sign in anonymously when currentUser is null.
- **Not handling auth/email-already-in-use when linking, losing the anonymous user's data** — undefined Fix: Catch this error and offer to sign in with the existing account instead. Manually merge the anonymous user's data (cart, preferences) into the existing account before deleting the anonymous UID's data.
- **Forgetting to enable Anonymous sign-in in the Firebase Console, getting auth/operation-not-allowed** — undefined Fix: Go to Firebase Console > Authentication > Sign-in method and enable the Anonymous provider.

## Best practices

- Check for an existing auth session before calling signInAnonymously to avoid creating duplicate accounts
- Always offer account linking before any permanent action like checkout or data export
- Handle auth/email-already-in-use and auth/credential-already-in-use errors with data merge logic
- Use security rules to differentiate between anonymous and permanent users for sensitive operations
- Clean up abandoned anonymous accounts with a scheduled Cloud Function or Identity Platform auto-delete
- Store anonymous user data with their UID so it persists after account linking
- Show a non-intrusive banner prompting anonymous users to create an account after they have engaged

## Frequently asked questions

### Do anonymous users count toward Firebase Auth billing?

On the standard Firebase Auth plan, anonymous users are free and unlimited. On Identity Platform, anonymous users count toward the 50,000 free MAU limit, then cost approximately $0.0055 per additional MAU.

### What happens to anonymous user data when they link to a permanent account?

The UID stays the same after linking, so all Firestore documents, Storage files, and other data keyed by UID remain accessible. No data migration is needed.

### Can I sign in anonymously multiple times and get different UIDs?

If no user is currently signed in, each call to signInAnonymously creates a new user with a new UID. If an anonymous user is already signed in, the existing session is reused.

### How do I delete an anonymous user's data when they sign out?

Store the UID before signing out, then use the Admin SDK or security rules to delete their Firestore and Storage data. You can also use a Cloud Function triggered by Auth user deletion.

### Are anonymous accounts automatically deleted?

Only if you upgrade to Firebase Identity Platform, which auto-deletes anonymous accounts after 30 days of inactivity. On standard Auth, anonymous accounts persist until manually deleted.

### Can RapidDev help implement anonymous-to-permanent account conversion flows?

Yes, RapidDev's engineering team can build complete guest-to-user conversion flows including anonymous auth, account linking, data merging, and cleanup of abandoned accounts.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-handle-anonymous-users-in-firebase
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-handle-anonymous-users-in-firebase
