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.
Prerequisites
- 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
Sign in anonymously
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.
1import { getAuth, signInAnonymously, onAuthStateChanged } from 'firebase/auth'23const auth = getAuth()45async function signInAsGuest() {6 try {7 const userCredential = await signInAnonymously(auth)8 const user = userCredential.user9 console.log('Anonymous UID:', user.uid)10 console.log('Is anonymous:', user.isAnonymous) // true11 return user12 } catch (error: any) {13 console.error('Anonymous sign-in failed:', error.code)14 throw error15 }16}Expected result: A new anonymous user is created with a unique UID, and isAnonymous is true.
Detect whether the current user is anonymous
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.
1import { getAuth, onAuthStateChanged } from 'firebase/auth'2import { useEffect, useState } from 'react'34function useAuthState() {5 const [user, setUser] = useState<any>(null)6 const [isAnonymous, setIsAnonymous] = useState(false)7 const [loading, setLoading] = useState(true)89 useEffect(() => {10 const auth = getAuth()11 const unsubscribe = onAuthStateChanged(auth, (firebaseUser) => {12 setUser(firebaseUser)13 setIsAnonymous(firebaseUser?.isAnonymous ?? false)14 setLoading(false)15 })16 return () => unsubscribe()17 }, [])1819 return { user, isAnonymous, loading }20}2122// Usage in a component:23// const { user, isAnonymous } = useAuthState()24// if (isAnonymous) show 'Create account' bannerExpected result: The hook correctly identifies anonymous users and updates when they convert to permanent accounts.
Link the anonymous account to email/password
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.
1import { getAuth, EmailAuthProvider, linkWithCredential } from 'firebase/auth'23const auth = getAuth()45async function linkEmailPassword(email: string, password: string) {6 const user = auth.currentUser7 if (!user || !user.isAnonymous) {8 throw new Error('No anonymous user to link')9 }1011 const credential = EmailAuthProvider.credential(email, password)1213 try {14 const result = await linkWithCredential(user, credential)15 console.log('Account linked. UID preserved:', result.user.uid)16 console.log('Is anonymous:', result.user.isAnonymous) // false17 return result.user18 } catch (error: any) {19 if (error.code === 'auth/email-already-in-use') {20 console.error('This email is already registered. Sign in instead.')21 }22 throw error23 }24}Expected result: The anonymous account is upgraded to email/password with the same UID. isAnonymous becomes false.
Link the anonymous account to a social provider
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.
1import { getAuth, GoogleAuthProvider, linkWithPopup } from 'firebase/auth'23const auth = getAuth()45async function linkGoogleAccount() {6 const user = auth.currentUser7 if (!user || !user.isAnonymous) {8 throw new Error('No anonymous user to link')9 }1011 const provider = new GoogleAuthProvider()1213 try {14 const result = await linkWithPopup(user, provider)15 console.log('Google account linked. UID:', result.user.uid)16 return result.user17 } catch (error: any) {18 if (error.code === 'auth/credential-already-in-use') {19 console.error('This Google account is linked to another user.')20 // Consider merging data from anonymous to existing account21 }22 throw error23 }24}Expected result: The Google account is linked and the anonymous user becomes a permanent Google-authenticated user.
Write security rules that work with anonymous users
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.
1// firestore.rules2rules_version = '2';3service cloud.firestore {4 match /databases/{database}/documents {56 // Public data: any authenticated user (including anonymous) can read7 match /products/{productId} {8 allow read: if request.auth != null;9 allow write: if request.auth != null10 && request.auth.token.firebase.sign_in_provider != 'anonymous';11 }1213 // User data: scoped to their UID (works for both anonymous and permanent)14 match /carts/{userId}/{document=**} {15 allow read, write: if request.auth != null16 && request.auth.uid == userId;17 }1819 // Orders: require a permanent account20 match /orders/{orderId} {21 allow create: if request.auth != null22 && request.auth.token.firebase.sign_in_provider != 'anonymous';23 allow read: if request.auth != null24 && request.auth.uid == resource.data.userId;25 }26 }27}Expected result: Anonymous users can browse and save cart items but must create a permanent account to place orders.
Clean up abandoned anonymous accounts
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.
1// Cloud Function to clean up old anonymous accounts2import { onSchedule } from 'firebase-functions/v2/scheduler'3import { getAuth } from 'firebase-admin/auth'4import { initializeApp } from 'firebase-admin/app'56initializeApp()78export const cleanupAnonymousUsers = onSchedule('every day 02:00', async () => {9 const auth = getAuth()10 const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000 // 30 days1112 let nextPageToken: string | undefined13 do {14 const listResult = await auth.listUsers(1000, nextPageToken)15 const toDelete = listResult.users16 .filter(user => {17 const isAnon = user.providerData.length === 018 const lastSignIn = new Date(user.metadata.lastSignInTime).getTime()19 return isAnon && lastSignIn < cutoff20 })21 .map(user => user.uid)2223 if (toDelete.length > 0) {24 await auth.deleteUsers(toDelete)25 console.log(`Deleted ${toDelete.length} anonymous users`)26 }2728 nextPageToken = listResult.pageToken29 } while (nextPageToken)30})Expected result: Anonymous accounts older than 30 days are automatically deleted on a daily schedule.
Complete working example
1import {2 getAuth,3 signInAnonymously,4 onAuthStateChanged,5 EmailAuthProvider,6 GoogleAuthProvider,7 linkWithCredential,8 linkWithPopup,9 User,10} from 'firebase/auth'1112const auth = getAuth()1314export async function signInAsGuest(): Promise<User> {15 const { user } = await signInAnonymously(auth)16 return user17}1819export function onAuthChange(20 callback: (user: User | null, isAnonymous: boolean) => void21): () => void {22 return onAuthStateChanged(auth, (user) => {23 callback(user, user?.isAnonymous ?? false)24 })25}2627export async function upgradeToEmail(28 email: string,29 password: string30): Promise<User> {31 const user = auth.currentUser32 if (!user?.isAnonymous) throw new Error('Not an anonymous user')33 const credential = EmailAuthProvider.credential(email, password)34 const { user: linked } = await linkWithCredential(user, credential)35 return linked36}3738export async function upgradeToGoogle(): Promise<User> {39 const user = auth.currentUser40 if (!user?.isAnonymous) throw new Error('Not an anonymous user')41 const provider = new GoogleAuthProvider()42 const { user: linked } = await linkWithPopup(user, provider)43 return linked44}4546export function isGuest(): boolean {47 return auth.currentUser?.isAnonymous ?? false48}4950export function getCurrentUid(): string | null {51 return auth.currentUser?.uid ?? null52}Common mistakes when handling Anonymous Users in Firebase Auth
Why it's a problem: Creating a new anonymous user on every page load instead of checking for an existing session
How to avoid: 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.
Why it's a problem: Not handling auth/email-already-in-use when linking, losing the anonymous user's data
How to avoid: 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.
Why it's a problem: Forgetting to enable Anonymous sign-in in the Firebase Console, getting auth/operation-not-allowed
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to let users try my app without signing up using Firebase anonymous auth. Show me how to sign in anonymously, detect anonymous users, and then link the anonymous account to email/password or Google when they decide to create a permanent account. Include Firestore security rules that differentiate between anonymous and permanent users.
Implement Firebase anonymous authentication with account linking. Create functions for signInAnonymously, detecting isAnonymous, upgrading to email/password with linkWithCredential, and upgrading to Google with linkWithPopup. Include error handling for credential-already-in-use and Firestore security rules.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation