Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Store Additional User Data in Firebase Auth

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.

What you'll learn

  • How to create a Firestore profiles collection linked to Firebase Auth UIDs
  • How to write a user profile document after sign-up with setDoc
  • How to read and update user profile data alongside auth state
  • How to use custom claims for role-based access control
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read15-20 minFirebase Auth + Firestore (Spark and Blaze plans), firebase v9+ modular SDKMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1import { getFirestore, doc, setDoc, serverTimestamp } from 'firebase/firestore';
2import { getAuth, createUserWithEmailAndPassword } from 'firebase/auth';
3
4const db = getFirestore();
5const auth = getAuth();
6
7async function signUpAndCreateProfile(
8 email: string,
9 password: string,
10 displayName: string
11) {
12 // 1. Create the Firebase Auth account
13 const { user } = await createUserWithEmailAndPassword(auth, email, password);
14
15 // 2. Create a Firestore profile document with the same UID
16 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 });
25
26 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.

2

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.

typescript
1rules_version = '2';
2service cloud.firestore {
3 match /databases/{database}/documents {
4 match /users/{userId} {
5 // Users can read their own profile
6 allow read: if request.auth != null && request.auth.uid == userId;
7
8 // Users can create their own profile during sign-up
9 allow create: if request.auth != null
10 && request.auth.uid == userId
11 && request.resource.data.keys().hasAll(['email', 'displayName', 'role'])
12 && request.resource.data.role == 'member';
13
14 // Users can update their own profile (but not role or createdAt)
15 allow update: if request.auth != null
16 && request.auth.uid == userId
17 && !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.

3

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.

typescript
1import { getAuth, onAuthStateChanged, User } from 'firebase/auth';
2import { getFirestore, doc, getDoc } from 'firebase/firestore';
3
4interface UserProfile {
5 email: string;
6 displayName: string;
7 photoURL: string | null;
8 role: string;
9 bio: string;
10}
11
12const auth = getAuth();
13const db = getFirestore();
14
15onAuthStateChanged(auth, async (user: User | null) => {
16 if (user) {
17 // Fetch the Firestore profile
18 const profileDoc = await getDoc(doc(db, 'users', user.uid));
19
20 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 data
24 } else {
25 console.warn('No profile found for user:', user.uid);
26 }
27 } else {
28 // User is signed out — clear profile state
29 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.

4

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.

typescript
1import { getFirestore, doc, updateDoc, serverTimestamp } from 'firebase/firestore';
2import { getAuth } from 'firebase/auth';
3
4const db = getFirestore();
5const auth = getAuth();
6
7async 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');
14
15 await updateDoc(doc(db, 'users', user.uid), {
16 ...updates,
17 updatedAt: serverTimestamp()
18 });
19
20 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.

5

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.

typescript
1// Cloud Function to set custom claims (server-side)
2// functions/src/index.ts
3import { onCall, HttpsError } from 'firebase-functions/v2/https';
4import { getAuth } from 'firebase-admin/auth';
5import { initializeApp } from 'firebase-admin/app';
6
7initializeApp();
8
9export const setUserRole = onCall(async (request) => {
10 // Only admins can set roles
11 if (request.auth?.token?.role !== 'admin') {
12 throw new HttpsError('permission-denied', 'Only admins can set roles');
13 }
14
15 const { uid, role } = request.data;
16 if (!uid || !role) {
17 throw new HttpsError('invalid-argument', 'uid and role required');
18 }
19
20 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

user-profile.ts
1// Complete user profile management with Firebase Auth + Firestore
2
3import { initializeApp } from 'firebase/app';
4import {
5 getAuth,
6 createUserWithEmailAndPassword,
7 onAuthStateChanged,
8 User
9} from 'firebase/auth';
10import {
11 getFirestore,
12 doc,
13 setDoc,
14 getDoc,
15 updateDoc,
16 serverTimestamp
17} from 'firebase/firestore';
18
19const 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};
27
28const app = initializeApp(firebaseConfig);
29const auth = getAuth(app);
30const db = getFirestore(app);
31
32export 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}
41
42// Sign up and create a profile document
43export async function signUpWithProfile(
44 email: string,
45 password: string,
46 displayName: string
47): Promise<User> {
48 const { user } = await createUserWithEmailAndPassword(auth, email, password);
49
50 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 });
59
60 return user;
61}
62
63// Fetch user profile from Firestore
64export 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}
68
69// Update editable profile fields
70export 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');
77
78 await updateDoc(doc(db, 'users', user.uid), {
79 ...updates,
80 updatedAt: serverTimestamp()
81 });
82}
83
84// Listen for auth changes and fetch profile
85export function onAuthWithProfile(
86 callback: (user: User | null, profile: UserProfile | null) => void
87): () => 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.

ChatGPT Prompt

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.

Firebase Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.