Last updated: 23 October 2026. Includes Firebase's 2026 change that moved Cloud Storage to the Blaze plan.
Firebase is Google's backend-as-a-service: it gives you sign-in, a database, file storage and web hosting without running your own server. This guide sets up all four in a web app with the modular JavaScript SDK, and, more importantly, shows the security rules that stop other users from reading or deleting your users' data. Most beginner tutorials skip that part.
Contents
- Create a project and connect your app
- Authentication: email and Google sign-in
- Cloud Firestore: save and read data
- Cloud Storage: upload files
- Security rules for Firestore and Storage
- Hosting: deploy your app
- Common errors and fixes
- How the four fit together
- FAQs
1. Create a project and connect your app
- Go to the Firebase console and click Create a project.
- In Project settings → General → Your apps, click the web icon (
</>) and register your app. Firebase shows a config object; copy it. - Install the SDK in your project:
npm install firebase
Create src/firebase.js. Put every service you'll use in this one file and import from it everywhere else:
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT_ID.firebasestorage.app",
messagingSenderId: "YOUR_SENDER_ID",
appId: "YOUR_APP_ID"
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export const db = getFirestore(app);
export const storage = getStorage(app);
Note: projects created since late 2024 get a storage bucket ending in .firebasestorage.app. Older projects use .appspot.com. Copy whatever your console shows rather than typing it.
Is the API key a secret? No. The web config is meant to be public and ends up in your built JavaScript anyway. Your data is protected by security rules (section 5), not by hiding this key.
2. Authentication: email and Google sign-in
First enable the providers you need in Authentication → Sign-in method. If you skip this, sign-in fails with auth/operation-not-allowed.
Sign up and sign in with email
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut
} from "firebase/auth";
import { auth } from "./firebase";
export const signUp = (email, password) =>
createUserWithEmailAndPassword(auth, email, password);
export const signIn = (email, password) =>
signInWithEmailAndPassword(auth, email, password);
export const logout = () => signOut(auth);
Each function returns a promise, so wrap calls in try/catch and show error.code to the user. For example, auth/email-already-in-use or auth/invalid-credential.
Google sign-in
import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { auth } from "./firebase";
export const signInWithGoogle = () =>
signInWithPopup(auth, new GoogleAuthProvider());
Firebase also supports phone, anonymous, GitHub, Microsoft, Apple and other providers. Each one needs its own setup in the console; see the Firebase Auth docs.
Know who is signed in
Don't read auth.currentUser right when the page loads; it's null until Firebase restores the session. Listen for changes instead:
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "./firebase";
onAuthStateChanged(auth, (user) => {
if (user) {
console.log("Signed in:", user.uid, user.email);
} else {
console.log("Signed out");
}
});
The user.uid is the key to everything that follows: we use it to name each user's Firestore document and Storage folder.
3. Cloud Firestore: save and read data
Firestore is a NoSQL database. Data lives in documents (like JSON objects) inside collections. When you create the database in the console, pick production mode, which denies all access until you write rules. Test mode leaves the database open to anyone for 30 days, which is how many beginner projects get their data wiped.
Save a user's profile under their uid
Using the uid as the document ID makes the security rules simple later:
import { doc, setDoc, serverTimestamp } from "firebase/firestore";
import { auth, db } from "./firebase";
export const saveProfile = async (name) => {
const uid = auth.currentUser.uid;
await setDoc(doc(db, "users", uid), {
name,
email: auth.currentUser.email,
createdAt: serverTimestamp()
}, { merge: true });
};
Add, read, update and delete notes
Store each user's items in a subcollection, users/{uid}/notes:
import {
collection, addDoc, getDocs, updateDoc, deleteDoc, doc,
query, orderBy, serverTimestamp
} from "firebase/firestore";
import { auth, db } from "./firebase";
const notesRef = () => collection(db, "users", auth.currentUser.uid, "notes");
export const addNote = (text) =>
addDoc(notesRef(), { text, createdAt: serverTimestamp() });
export const getNotes = async () => {
const snap = await getDocs(query(notesRef(), orderBy("createdAt", "desc")));
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
};
export const updateNote = (id, text) =>
updateDoc(doc(notesRef(), id), { text });
export const deleteNote = (id) => deleteDoc(doc(notesRef(), id));
Real-time updates
onSnapshot calls your function immediately and again whenever the data changes, on any device:
import { onSnapshot, query, orderBy, collection } from "firebase/firestore";
import { auth, db } from "./firebase";
const q = query(
collection(db, "users", auth.currentUser.uid, "notes"),
orderBy("createdAt", "desc")
);
const unsubscribe = onSnapshot(q, (snap) => {
const notes = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
console.log(notes);
});
// In React, return unsubscribe from useEffect so the listener stops on unmount.
4. Cloud Storage: upload files
Plan change in 2026: since 3 February 2026, Cloud Storage for Firebase requires the pay-as-you-go Blaze plan, even for small projects. On the free Spark plan, storage requests fail with 402 or 403 errors. You can still pay nothing by staying within Google Cloud Storage's always-free usage, but you must add a billing account. Set a budget alert when you do. Details: Firebase Storage pricing FAQ.
Upload with progress and get a download URL
Save files under the user's own folder, users/{uid}/, so the rules can restrict access. Adding a timestamp avoids overwriting two files with the same name:
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import { auth, storage } from "./firebase";
export const uploadFile = (file, onProgress) => {
const path = `users/${auth.currentUser.uid}/${Date.now()}_${file.name}`;
const task = uploadBytesResumable(ref(storage, path), file);
return new Promise((resolve, reject) => {
task.on("state_changed",
(s) => onProgress?.(Math.round((s.bytesTransferred / s.totalBytes) * 100)),
reject,
async () => resolve({ path, url: await getDownloadURL(task.snapshot.ref) })
);
});
};
Save the returned path in Firestore so you can delete the file later:
import { ref, deleteObject } from "firebase/storage";
import { storage } from "./firebase";
export const deleteFile = (path) => deleteObject(ref(storage, path));
5. Security rules for Firestore and Storage
Rules run on Google's servers and decide every read and write. Without them, anyone who copies your public config can access your data. These rules let each signed-in user read and write only their own data.
Firestore rules
In the console, open Firestore Database → Rules, replace the contents and click Publish:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Each user can read and write only their own profile and notes
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
match /notes/{noteId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
// Everything else is denied by default
}
}
Storage rules
In Storage → Rules. This also limits uploads to images under 5 MB:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /users/{userId}/{fileName} {
allow read: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null
&& request.auth.uid == userId
&& (request.resource == null
|| (request.resource.size < 5 * 1024 * 1024
&& request.resource.contentType.matches('image/.*')));
}
}
}
Test your rules before publishing with the Rules Playground button on the same page: simulate a request as a signed-in user and as an anonymous one, and check that only the right one is allowed.
6. Hosting: deploy your app
Firebase Hosting serves your site from a global CDN with free SSL and a your-project.web.app address. Hosting still works on the free Spark plan.
# 1. Install the CLI once
npm install -g firebase-tools
# 2. Sign in (opens a browser)
firebase login
# 3. In your project folder
firebase init hosting
Answer the questions:
- Project: use an existing project, and pick yours.
- Public directory:
distfor Vite,buildfor older create-react-app projects. - Single-page app: Yes, if you use React Router. Otherwise refreshing a page gives a 404.
- Overwrite index.html: No.
# 4. Build, then deploy
npm run build
firebase deploy --only hosting
# Optional: a temporary preview URL that expires after 7 days
firebase hosting:channel:deploy preview
Google sign-in after deploying: add your live domain in Authentication → Settings → Authorized domains. web.app and firebaseapp.com are added automatically; a custom domain is not.
To connect your own domain, open Hosting → Add custom domain and add the DNS records it shows at your domain registrar. SSL is set up automatically once DNS updates.
7. Common errors and fixes
| Error | Cause | Fix |
|---|---|---|
auth/operation-not-allowed | Sign-in provider not enabled | Enable it in Authentication → Sign-in method |
auth/unauthorized-domain | Google sign-in from a domain Firebase doesn't know | Add the domain in Authentication → Settings → Authorized domains |
auth/popup-blocked | Browser blocked the sign-in popup | Call signInWithPopup directly from a button click |
permission-denied (Firestore) or storage/unauthorized | Your rules rejected the request | Check the user is signed in and the path uses their uid; test in Rules Playground |
| Storage returns 402 or 403 on every request | Project is on the Spark plan | Upgrade to Blaze (see section 4) |
| Refreshing a page on the live site shows 404 | Single-page app rewrite not set | Run firebase init hosting again and answer Yes to single-page app |
8. How the four fit together
In a typical app, such as a notes or social app:
- Auth answers who the user is and gives you their
uid. - Firestore stores their data at
users/{uid}/…. - Storage stores their files at
users/{uid}/…, with the file path saved in Firestore. - Security rules use the same
uidto keep each user's data private. - Hosting serves the whole app.
Using the uid consistently in paths is what makes the rules short and safe.
FAQs
Is Firebase free?
Authentication, Firestore and Hosting have free usage on the Spark plan, enough for learning and small projects. Cloud Storage and Cloud Functions need the Blaze plan, though you pay nothing if you stay within the free usage. Check the current limits on the Firebase pricing page.
Can I use Firebase with Android or React Native?
Yes. Android apps use the Firebase Android SDK (see my Android developer roadmap). For React Native, React Native Firebase wraps the native SDKs.
Firestore or Realtime Database?
For new projects, Firestore. It has better queries and scales further. Realtime Database is simpler and suits very small, frequently changing data such as presence indicators.
Should I hide my Firebase config in a .env file?
You can, to keep it tidy and switch between projects, but it doesn't make it secret: it still ends up in the browser. Security rules are what protect your data.
Can I use a custom domain with Firebase Hosting?
Yes, free, with automatic SSL. See the end of section 6.
My own Android apps on Google Play: Vidyavan and Android UI Kit.
