When you start building an app with Firebase, the first thing you reach for is Firebase Authentication. It handles sign-up, login, password resets, social logins, and session management out of the box. It's fast to integrate and removes a huge amount of boilerplate.
But it doesn't take long before you run into a wall.
Your product manager asks: "Can we store the user's preferred language?" or "Can we keep track of whether they've completed onboarding?" You open the Firebase Authentication documentation and realise that the built-in user object only holds a small, fixed set of fields.
This is the problem a UserProfile collection in Firestore solves.
What Firebase Authentication Actually Stores
When a user signs up with Firebase, the Authentication module creates a record with a limited set of built-in fields. These are the only fields that live directly on the Firebase Auth user object:
| Field | Description |
|---|---|
uid |
A unique identifier for the user (auto-generated) |
email |
The user's email address |
phoneNumber |
An optional phone number |
emailVerified |
Whether the email has been verified |
isAnonymous |
Whether the user is anonymous |
That's it. There is no way to add custom fields directly to this object. If you want to store anything beyond this — a username, a bio, a theme preference, an onboarding flag, a subscription tier — you need a separate data store.
The Solution: A Firestore user_profiles Collection
The standard pattern is to create a dedicated collection in Cloud Firestore where each document represents one user's extended profile. The document's ID is set to the user's Firebase Authentication uid. This creates a direct, reliable link between the two records.
Designing the user_profiles Document
A well-designed user_profiles document typically looks like this:
user_profiles (collection)
└── {uid} (document)
├── uid: string
├── display_name: string
├── email: string
├── photo_url: string
├── bio: string
├── preferred_language: string
├── onboarding_complete: boolean
├── theme: string // "light" | "dark" | "system"
├── notifications_enabled: boolean
├── created_at: timestamp
└── updated_at: timestamp
Storing uid and email directly inside the Firestore document (even though they already exist in Authentication) is intentional. It makes queries simpler and avoids having to cross-reference two systems when you only have the Firestore document at hand.
Creating the UserProfile at Sign-Up
The critical rule is: when you create an Auth user, you must immediately create the corresponding Firestore document in the same flow. If you defer this step, you risk ending up with authenticated users who have no profile document — which will cause null errors and a poor user experience.
The safest place to do this is right after the createUserWithEmailAndPassword call returns successfully.
Flutter Example: Sign-Up with Profile Creation
Here is a complete sign-up function in Flutter that creates both the Auth user and the Firestore profile atomically:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<void> signUpUser({
required String email,
required String password,
required String displayName,
}) async {
// Step 1: Create the user in Firebase Authentication.
final UserCredential credential = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
final User user = credential.user!;
// Step 2: Create the matching UserProfile document in Firestore.
// The document ID is set to the user's uid to make lookups O(1).
await _firestore.collection('user_profiles').doc(user.uid).set({
'uid': user.uid,
'display_name': displayName,
'email': email,
'photo_url': '',
'bio': '',
'preferred_language': 'en',
'onboarding_complete': false,
'theme': 'system',
'notifications_enabled': true,
'created_at': FieldValue.serverTimestamp(),
'updated_at': FieldValue.serverTimestamp(),
});
}
Reading the UserProfile
Once the profile document exists, fetching it anywhere in your app is straightforward:
Future<Map<String, dynamic>?> getUserProfile(String uid) async {
final DocumentSnapshot doc =
await _firestore.collection('user_profiles').doc(uid).get();
if (doc.exists) {
return doc.data() as Map<String, dynamic>;
}
return null;
}