Firebase Auth stores only basic user fields like email, displayName, and photoURL. To store additional data like phone number, address, or user roles, create a users collection in Firestore where each document ID matches the Firebase Auth UID. Write a user profile document immediately after sign-up using setDoc, and read it alongside the auth state with onAuthStateChanged. For small role-based data, use custom claims set via the Admin SDK.
Storing Additional User Data Beyond Firebase Auth Defaults
Firebase Auth provides basic user properties like email, displayName, photoURL, and phoneNumber, but most applications need to store more: user roles, preferences, addresses, or profile details. This tutorial shows how to create a Firestore users collection that mirrors your Auth users, write profile data during sign-up, keep it in sync, and use custom claims for access control.
Prerequisites
- A Firebase project with Authentication and Firestore enabled
- Firebase SDK installed (npm install firebase)
- At least one sign-in method enabled in Firebase Console > Authentication > Sign-in method
- Basic understanding of Firestore document structure
Step-by-step guide
Create a Firestore users collection with Auth UID as document ID
Create a Firestore users collection with Auth UID as document ID
The standard pattern is to create a users collection in Firestore where each document ID matches the Firebase Auth UID (user.uid). This creates a one-to-one mapping between auth accounts and profile data. Using the UID as the document ID makes it easy to fetch a user's profile with a single getDoc call and simplifies security rules since you can compare request.auth.uid to the document ID.
1import { getFirestore, doc, setDoc, serverTimestamp } from 'firebase/firestore';2import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';34const db = getFirestore();5const auth = getAuth();67async function signUpAndCreateProfile(8 email: string,9 password: string,10 displayName: string11) {12 // 1. Create the Firebase Auth account13 const { user } = await createUserWithEmailAndPassword(auth, email, password);1415 // 2. Create a Firestore profile document with the same UID16 await setDoc(doc(db, 'users', user.uid), {17 email: user.email,18 displayName: displayName,19 photoURL: null,20 role: 'member',21 bio: '',22 createdAt: serverTimestamp(),23 updatedAt: serverTimestamp()24 });2526 return user;27}Expected result: A new auth account is created and a matching profile document appears in the Firestore users collection with the same ID.
Write Firestore security rules for the users collection
Write Firestore security rules for the users collection
Secure the users collection so each user can only read and write their own profile document. The rule compares request.auth.uid to the document ID ({userId}) to enforce ownership. Add data validation to prevent users from setting invalid roles or modifying protected fields like createdAt.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {4 match /users/{userId} {5 // Users can read their own profile6 allow read: if request.auth != null && request.auth.uid == userId;78 // Users can create their own profile during sign-up9 allow create: if request.auth != null10 && request.auth.uid == userId11 && request.resource.data.keys().hasAll(['email', 'displayName', 'role'])12 && request.resource.data.role == 'member';1314 // Users can update their own profile (but not role or createdAt)15 allow update: if request.auth != null16 && request.auth.uid == userId17 && !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role', 'createdAt']);18 }19 }20}Expected result: Users can create and update their own profile but cannot modify protected fields like role or createdAt.
Read user profile data alongside auth state
Read user profile data alongside auth state
When the app loads, onAuthStateChanged fires with the current user. Use this event to fetch the user's Firestore profile document. Combine both the auth user and Firestore profile into a single state object that your components can consume. This ensures you always have the latest profile data when the user's auth state changes.
1import { getAuth, onAuthStateChanged, User } from 'firebase/auth';2import { getFirestore, doc, getDoc } from 'firebase/firestore';34interface UserProfile {5 email: string;6 displayName: string;7 photoURL: string | null;8 role: string;9 bio: string;10}1112const auth = getAuth();13const db = getFirestore();1415onAuthStateChanged(auth, async (user: User | null) => {16 if (user) {17 // Fetch the Firestore profile18 const profileDoc = await getDoc(doc(db, 'users', user.uid));1920 if (profileDoc.exists()) {21 const profile = profileDoc.data() as UserProfile;22 console.log('User profile:', profile);23 // Set your app state with the combined user + profile data24 } else {25 console.warn('No profile found for user:', user.uid);26 }27 } else {28 // User is signed out — clear profile state29 console.log('No user signed in');30 }31});Expected result: When a user signs in, the app fetches their Firestore profile alongside the auth state and makes it available to components.
Update user profile fields from the client
Update user profile fields from the client
Allow users to update editable fields like displayName, bio, and photoURL through a profile edit form. Use updateDoc to modify only the changed fields without overwriting the entire document. Always include an updatedAt timestamp to track when the profile was last modified. Remember that the security rules prevent users from changing protected fields like role.
1import { getFirestore, doc, updateDoc, serverTimestamp } from 'firebase/firestore';2import { getAuth } from 'firebase/auth';34const db = getFirestore();5const auth = getAuth();67async function updateProfile(updates: {8 displayName?: string;9 bio?: string;10 photoURL?: string | null;11}) {12 const user = auth.currentUser;13 if (!user) throw new Error('Not authenticated');1415 await updateDoc(doc(db, 'users', user.uid), {16 ...updates,17 updatedAt: serverTimestamp()18 });1920 console.log('Profile updated successfully');21}Expected result: The user's Firestore profile is updated with the new fields and the updatedAt timestamp is refreshed.
Use custom claims for role-based access control
Use custom claims for role-based access control
For access control data like user roles (admin, editor, member), Firebase custom claims are more secure than Firestore fields because they are embedded in the ID token and enforced at the security rules level without a database read. Custom claims must be set from a server environment using the Firebase Admin SDK, typically through a Cloud Function. The claims are available in security rules via request.auth.token.
1// Cloud Function to set custom claims (server-side)2// functions/src/index.ts3import { onCall, HttpsError } from 'firebase-functions/v2/https';4import { getAuth } from 'firebase-admin/auth';5import { initializeApp } from 'firebase-admin/app';67initializeApp();89export const setUserRole = onCall(async (request) => {10 // Only admins can set roles11 if (request.auth?.token?.role !== 'admin') {12 throw new HttpsError('permission-denied', 'Only admins can set roles');13 }1415 const { uid, role } = request.data;16 if (!uid || !role) {17 throw new HttpsError('invalid-argument', 'uid and role required');18 }1920 await getAuth().setCustomUserClaims(uid, { role });21 return { success: true };22});Expected result: The user's custom claims are set on the server. The claims are included in the ID token on next refresh and available in security rules via request.auth.token.role.
Complete working example
1// Complete user profile management with Firebase Auth + Firestore23import { initializeApp } from 'firebase/app';4import {5 getAuth,6 createUserWithEmailAndPassword,7 onAuthStateChanged,8 User9} from 'firebase/auth';10import {11 getFirestore,12 doc,13 setDoc,14 getDoc,15 updateDoc,16 serverTimestamp17} from 'firebase/firestore';1819const firebaseConfig = {20 apiKey: 'YOUR_API_KEY',21 authDomain: 'YOUR_PROJECT.firebaseapp.com',22 projectId: 'YOUR_PROJECT_ID',23 storageBucket: 'YOUR_PROJECT.appspot.com',24 messagingSenderId: 'YOUR_SENDER_ID',25 appId: 'YOUR_APP_ID'26};2728const app = initializeApp(firebaseConfig);29const auth = getAuth(app);30const db = getFirestore(app);3132export interface UserProfile {33 email: string;34 displayName: string;35 photoURL: string | null;36 role: string;37 bio: string;38 createdAt: any;39 updatedAt: any;40}4142// Sign up and create a profile document43export async function signUpWithProfile(44 email: string,45 password: string,46 displayName: string47): Promise<User> {48 const { user } = await createUserWithEmailAndPassword(auth, email, password);4950 await setDoc(doc(db, 'users', user.uid), {51 email: user.email,52 displayName,53 photoURL: null,54 role: 'member',55 bio: '',56 createdAt: serverTimestamp(),57 updatedAt: serverTimestamp()58 });5960 return user;61}6263// Fetch user profile from Firestore64export async function getUserProfile(uid: string): Promise<UserProfile | null> {65 const snap = await getDoc(doc(db, 'users', uid));66 return snap.exists() ? (snap.data() as UserProfile) : null;67}6869// Update editable profile fields70export async function updateUserProfile(updates: {71 displayName?: string;72 bio?: string;73 photoURL?: string | null;74}): Promise<void> {75 const user = auth.currentUser;76 if (!user) throw new Error('Not authenticated');7778 await updateDoc(doc(db, 'users', user.uid), {79 ...updates,80 updatedAt: serverTimestamp()81 });82}8384// Listen for auth changes and fetch profile85export function onAuthWithProfile(86 callback: (user: User | null, profile: UserProfile | null) => void87): () => void {88 return onAuthStateChanged(auth, async (user) => {89 if (user) {90 const profile = await getUserProfile(user.uid);91 callback(user, profile);92 } else {93 callback(null, null);94 }95 });96}Common mistakes when storing Additional User Data in Firebase Auth
Why it's a problem: Using addDoc instead of setDoc for the profile document, creating a random document ID instead of matching the Auth UID
How to avoid: Always use setDoc(doc(db, 'users', user.uid), data) so the document ID equals the Auth UID for easy lookups and simple security rules.
Why it's a problem: Allowing users to set their own role field from the client, creating a privilege escalation vulnerability
How to avoid: Never let the client write the role field. Use security rules to block role changes from clients, and set roles only via the Admin SDK or Cloud Functions.
Why it's a problem: Not handling the case where a user exists in Auth but has no Firestore profile document, causing the app to crash
How to avoid: Check profileDoc.exists() after getDoc and handle the missing profile case by creating a default profile or showing a profile completion form.
Why it's a problem: Storing large amounts of data in custom claims instead of Firestore, exceeding the 1000 byte limit
How to avoid: Use custom claims only for small access control data like roles and permissions. Store detailed profile information in Firestore.
Best practices
- Use the Firebase Auth UID as the Firestore document ID for a direct one-to-one mapping between auth accounts and profiles
- Create the Firestore profile document immediately after createUserWithEmailAndPassword in the same function
- Write security rules that compare request.auth.uid to the document ID to enforce per-user access
- Use custom claims for role-based access control and Firestore for detailed profile data
- Always include createdAt and updatedAt timestamps with serverTimestamp() for audit trails
- Handle the case where a profile document does not exist for legacy users or edge cases
- Use updateDoc for partial updates rather than setDoc which overwrites the entire document
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to store additional user data beyond what Firebase Auth provides. Show me how to create a Firestore users collection linked by UID, write profile data during sign-up, add security rules, and read the profile alongside onAuthStateChanged.
Create a complete user profile system using Firebase Auth and Firestore with the modular SDK v9+. Include sign-up with profile creation, security rules for the users collection, profile fetching in onAuthStateChanged, and a Cloud Function for setting custom claims.
Frequently asked questions
Why not just use updateProfile() from Firebase Auth to store extra data?
Firebase Auth's updateProfile() only supports displayName and photoURL fields. There is no way to add custom fields to the Auth user object. Firestore gives you unlimited custom fields per user.
Should I use custom claims or Firestore for user roles?
Use custom claims for roles that need to be checked in security rules (Firestore, Storage, Functions) without a database read. Use Firestore for role data that is displayed in the UI or needs complex querying. Many apps use both: custom claims for access control and Firestore for the full role description.
How do I keep the Firestore profile in sync if the user changes their email in Auth?
Set up a Cloud Function that listens to Auth events (onCreate, onUpdate) and automatically updates the corresponding Firestore document when Auth fields change.
What happens if the profile setDoc fails after createUserWithEmailAndPassword?
The user will have an Auth account but no Firestore profile. Handle this by checking if the profile exists on sign-in and creating it if missing, or by using a Cloud Function on Auth onCreate as a backup.
Can I query Firestore for users by custom fields like role?
Yes. You can query the users collection with where('role', '==', 'admin') to find all admin users. This is one advantage of storing roles in Firestore rather than only in custom claims.
How much data can I store in a Firestore user profile document?
A single Firestore document can be up to 1 MiB with up to 20,000 fields. This is far more than you would need for a user profile. For truly large data like file uploads, use Firebase Storage instead.
Can RapidDev help design and implement a user management system with Firebase?
Yes. RapidDev can build complete user management systems including profile storage, role-based access control, admin dashboards, and custom claims configuration using Firebase Auth and Firestore.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation