What You'll Learn
- What FCM V1 is and why it replaced the legacy API
- How to create a Firebase project and connect it to your app
- How to collect both Expo and FCM tokens from the device
- How to send text notifications via Expo and image notifications via FCM V1
- How to test everything without writing backend code
Prerequisites
Before starting, make sure you have:
- A React Native project using Expo (managed workflow)
- Node.js installed
- An Android device or emulator for testing
- A Firebase account (free) at console.firebase.google.com
Part 1 — Understanding FCM V1
What is FCM?
Firebase Cloud Messaging (FCM) is Google's free service for sending push notifications to Android, iOS, and web apps. Your backend server sends a message to FCM, and FCM delivers it to the user's device.
Why V1 instead of Legacy?
Google deprecated the legacy FCM API in June 2024. The new HTTP V1 API is more secure and more powerful.
| Feature | Legacy API | V1 API |
|---|---|---|
| Authentication | Static Server Key | OAuth 2.0 token (expires in 1hr) |
| Security | Key can be leaked | Short-lived tokens, harder to misuse |
| Image support | Limited | Full support |
| Per-platform config | Basic | Full Android / iOS / Web control |
| Status | ❌ Deprecated | ✅ Current standard |
The Hybrid Approach (Recommended)
Expo's push service is great for simple text notifications but strips image fields from the payload. So the best setup is:
Text notification → Your Backend → Expo Push API → Device
Image notification → Your Backend → FCM V1 API directly → Device
Your app registers two tokens — one Expo token and one FCM device token — and your backend picks the right channel based on whether the notification has an image.
Part 2 — Firebase Project Setup
Step 1 — Create a Firebase Project
- Go to console.firebase.google.com
- Click Add project
- Enter a project name (e.g.
MyApp) - Disable Google Analytics if you don't need it
- Click Create project
Step 2 — Add Your Android App
- In the Firebase console, click the Android icon to add an app
- Enter your app's package name — find it in
app.json - Click Register app
- Download the
google-services.jsonfile - Place it at the root of your project (same folder as
app.json)
iOS users: Repeat with the iOS icon, download
GoogleService-Info.plist, and place it at the project root too.
Step 3 — Get Your Service Account Key
You need this to generate OAuth tokens for the V1 API.
- In Firebase Console → Project Settings (gear icon)
- Click the Service accounts tab
- Click Generate new private key
- Download the JSON file — keep it safe, never commit it to git
Part 3 — App Setup
Step 4 — Install Dependencies
npx expo install expo-notifications expo-device @react-native-async-storage/async-storage
Step 5 — Update app.json
Add the google-services.json path and configure the notifications plugin:
{
"expo": {
"name": "MyApp",
"android": {
"package": "com.yourcompany.yourapp",
"googleServicesFile": "./google-services.json"
},
"plugins": [
[
"expo-notifications",
{
"icon": "./assets/icon.png",
"color": "#000000",
"defaultChannel": "default"
}
]
]
}
}
Important: The
google-services.jsonis a native file. Expo Go will not pick it up. You must build the app with EAS orexpo run:android.
Step 6 — Create NotificationService.js
This file handles permission requests and collects both tokens. The code registers for push notifications, creates an Android notification channel, requests permissions, and retrieves both the Expo push token and FCM device token.
Part 4 — Building the App
Because google-services.json is a native file, you cannot test FCM with Expo Go. You need a real build.
Option A — EAS Build (recommended)
# Install EAS CLI if you haven't
npm install -g eas-cli
# Log in to your Expo account
eas login
# Build for Android
npx eas build --platform android --profile development
Install the resulting APK on your device.
Option B — Local Build
npx expo run:android
This requires Android Studio and the Android SDK to be set up on your machine.
Part 5 — Sending Notifications
How the Backend Decides Which API to Use
# Python pseudocode
def send_notification(user, title, body, image_url=None):
if image_url:
# Use FCM V1 directly — supports images
send_via_fcm_v1(
fcm_token=user.fcm_token,
title=title,
body=body,
image=image_url
)
else:
# Use Expo — simpler for text-only
send_via_expo(
expo_token=user.expo_token,
title=title,
body=body
)
Sending via Expo Push API (text notifications)
// Node.js example
const response = await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: 'ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]',
title: 'Hello!',
body: 'This is a text notification.',
}),
});
Sending via FCM V1 API (image notifications)
First, generate an OAuth access token from your service account. Then send the FCM V1 request with the notification payload including image URLs for different platforms.
Image Requirements:
- Must be a public HTTPS URL (no redirects, no auth)
- Recommended size: 1024 × 512px (2:1 ratio)
- Format: JPG or PNG
- Max size: ~1MB (Android downloads it at delivery time)
Part 6 — Testing Without a Backend
You can test FCM V1 directly from a browser using the service account JSON to auto-generate OAuth tokens. Use an FCM test tool to generate tokens, paste your FCM device token, fill in notification details, and send test notifications.
Common Errors and Fixes
INVALID_ARGUMENT — Invalid registration token
The FCM device token is wrong or expired. Get a fresh one from the app.
401 Unauthorized
Your OAuth access token has expired (they last ~1 hour). Generate a new one.
SENDER_ID_MISMATCH
The google-services.json in your app doesn't match the Firebase project you're sending from. Make sure both are from the same project.
Image not showing on device
Check these:
- Image URL is HTTPS (not HTTP)
- URL is publicly accessible — open it in a browser to verify
- Image is under 1MB
- You included the
imagefield in bothnotificationandandroid.notification - App was built with
google-services.jsonincluded (not Expo Go)
Security Best Practices
| Rule | Why |
|---|---|
Never commit service-account.json to git |
It gives full access to your Firebase project |
Add it to .gitignore |
Prevents accidental commits |
| Never expose it in client-side code | Anyone can use it to send notifications |
| Rotate the key if it leaks | Firebase Console → Service Accounts → delete and regenerate |
| Use environment variables on your server | process.env.FIREBASE_KEY instead of hardcoded paths |
Summary
You've now set up a complete push notification system using Firebase Cloud Messaging V1 API with Expo. Your app collects both Expo and FCM tokens, and your backend can send text notifications via Expo's API and image notifications via FCM V1 directly.
Quick Checklist
- ✓ Firebase project created
- ✓ Android app added,
google-services.jsondownloaded and placed at project root - ✓
app.jsonupdated withgoogleServicesFileand notifications plugin - ✓ NotificationService.js collects both Expo and FCM tokens
- ✓ Both tokens saved to your backend on app launch
- ✓ App rebuilt with EAS or
expo run:android(not Expo Go) - ✓ Backend sends text via Expo API, images via FCM V1 API
- ✓ Image URLs are public HTTPS, 2:1 ratio, under 1MB
Useful Links
Written based on a real Expo + Firebase V1 setup. Last updated: May 2026.