Version 27 bundles several breaking behavior corrections and dependency bumps that landed after v26:
- Firebase JS SDK 12.18.0 — on web/macOS (
Platform.other),FunctionsError.messageappends an HTTP status suffix andcustomData.urlis populated (#9218). - Cloud Messaging — four documented runtime contracts are enforced after previous accidental shapes (#9246, #9247, #9252, #9254).
- Imagen API removed from
@react-native-firebase/aiafter Google shut down the Imagen models as a service on 2026-08-17. Generate images with a Gemini image model throughgetGenerativeModel()instead.
If you are upgrading from v25 or earlier, complete Migrating to v26 first — the namespaced API removal and New Architecture requirement still apply.
- Agent-assisted migration
- Firebase JS SDK 12.18.0
- Cloud Messaging
- Removed Imagen API
- Automated migration checklist
This guide is written to be complete enough for an automated first pass. We recommend feeding this document to your coding agent, then asking it to analyze your codebase against each relevant section below and produce a concrete change list (or PR) for the packages you use. The Automated migration checklist at the end is structured for that workflow.
PR: #9218
React Native Firebase pins firebase-js-sdk 12.18.0. On web and macOS (Platform.other), Cloud Functions uses the JS SDK callable path. That path now enriches FunctionsError (aliased as HttpsError in React Native Firebase):
| Change | Before | After (v27 / JS SDK 12.18.0) | Who |
|---|---|---|---|
FunctionsError.message | Exact server message string | Message plus a trailing [<httpStatus>] suffix (for example Invalid test requested. [400]) | Web / macOS only |
FunctionsError.customData.url | Not present | Request URL string on customData | Web / macOS only |
Native iOS and Android continue to surface callable failures through the native bridge as HttpsError without the HTTP status suffix. Do not assume the suffix or customData.url on those platforms.
- Search for exact-equality checks on
error.message/e.messagefor callable failures. - On web/macOS, prefer
message.startsWith(expected)or strip a trailing[digits]before comparing. - If you need the HTTP status or request URL, read them from the suffix /
customData.urlon web/macOS only; keep a native fallback path for iOS/Android.
// Previously (exact match — breaks on web/macOS after 12.18.0)
if (e.message === 'Invalid test requested.') {
/* … */
}
// Now (tolerant of the optional " [status]" suffix)
const base = e.message.replace(/ \[\d+\]$/, '');
if (base === 'Invalid test requested.') {
/* … */
}
// Optional on Platform.other:
// e.customData?.urlUpstream note: firebase@12.18.0 — “Add HTTP status code to the message field and add url to customData.”
These changes correct runtime behavior to match the public RemoteMessage / listener contracts. There is no new public TypeScript surface — consumers that worked around the old accidental shapes must update.
PR: #9246
String-valued APNs sounds are serialized at the documented message.notification.ios.sound path. Previously, only string sounds were placed at the undocumented message.notification.sound path (critical-sound dictionaries already used the correct iOS nesting).
| Before (accidental) | After (documented) |
|---|---|
message.notification.sound (string) | message.notification.ios.sound (string) |
// Previously
const sound = message.notification?.sound;
// Now
const sound = message.notification?.ios?.sound;PR: #9247
On iOS, RemoteMessage.sentTime is a number of epoch milliseconds, matching Android and the public type. FCM supplies seconds (often as a string); native serialization converts those values to milliseconds.
| Before (iOS accidental) | After |
|---|---|
string epoch seconds (for example "1522880044") | number epoch milliseconds (for example 1522880044000) |
Android behavior is unchanged. Remove parseInt / string comparisons that assumed seconds.
// Previously (iOS)
const sentMs = Number(message.sentTime) * 1000;
// Now (iOS and Android)
const sentMs = message.sentTime; // number, epoch msPR: #9252
iOS aps.badge is serialized as a string, matching RemoteMessage.notification.ios.badge?: string.
| Before (iOS accidental) | After |
|---|---|
number (for example 7) | string (for example "7") |
// Previously
const badge = message.notification?.ios?.badge; // number on iOS
// Now
const badge = Number(message.notification?.ios?.badge ?? 0);PR: #9254
onMessageSent listeners receive the documented message-ID string. Android previously forwarded the transport wrapper { messageId } to application code.
| Before (accidental) | After (documented) |
|---|---|
{ messageId: string } | string |
// Previously
onMessageSent(messaging, ({ messageId }) => {
console.log(messageId);
});
// Now
onMessageSent(messaging, messageId => {
console.log(messageId);
});Nothing else in @react-native-firebase/ai changed. getGenerativeModel(), getTemplateGenerativeModel(), TemplateGenerativeModel, and Gemini image generation are unaffected.
{/* prettier-ignore */}
| Removed | Replacement |
|---|---|
getImagenModel | getGenerativeModel() with a Gemini image model |
getTemplateImagenModel | getTemplateGenerativeModel() with a template that uses an image model |
ImagenModel | GenerativeModel |
TemplateImagenModel | TemplateGenerativeModel |
ImagenModelParams | ModelParams |
ImagenGenerationConfig | GenerationConfig with responseModalities and imageConfig |
ImagenImageFormat | No replacement — Gemini image models always return PNG; read inlineData.mimeType to confirm |
ImagenGenerationResponse | GenerateContentResult |
ImagenInlineImage | InlineDataPart on the response candidate |
ImagenGCSImage | No replacement — Gemini image models return inline data |
ImagenAspectRatio | ImageConfigAspectRatio |
ImagenSafetySettings | SafetySetting[] on getGenerativeModel() |
ImagenSafetyFilterLevel | HarmBlockThreshold |
ImagenPersonFilterLevel | No replacement — Gemini image models allow generating people by default |
// Previously (removed)
import { getApp } from '@react-native-firebase/app';
import { getAI, getImagenModel, ImagenAspectRatio } from '@react-native-firebase/ai';
const ai = getAI(getApp());
const imagenModel = getImagenModel(ai, {
model: 'imagen-3.0-generate-002',
generationConfig: {
aspectRatio: ImagenAspectRatio.LANDSCAPE_16x9,
},
});
const result = await imagenModel.generateImages('Draw a red bicycle on a beach at sunset');
console.log(result.images[0].mimeType);// Now
import { getApp } from '@react-native-firebase/app';
import {
getAI,
getGenerativeModel,
ImageConfigAspectRatio,
ImageConfigImageSize,
ResponseModality,
} from '@react-native-firebase/ai';
const ai = getAI(getApp());
const model = getGenerativeModel(ai, {
model: 'gemini-3.1-flash-image',
generationConfig: {
responseModalities: [ResponseModality.IMAGE],
imageConfig: {
aspectRatio: ImageConfigAspectRatio.LANDSCAPE_16x9,
imageSize: ImageConfigImageSize.SIZE_1K,
},
},
});
const result = await model.generateContent('Draw a red bicycle on a beach at sunset');
const imagePart = result.response.inlineDataParts()?.[0];
console.log(imagePart?.inlineData.mimeType);See AI Logic usage for more image generation examples, and Google's Imagen to Gemini migration guide for model and prompt guidance.
Use this section when running scripted or agent-assisted upgrades from v26 → v27.
# Bump all @react-native-firebase/* packages to v27 together (monorepo versions are aligned)
yarn add @react-native-firebase/app@^27.0.0 …
cd ios && pod installConfirm the lockfiles resolve firebase / firebase-js-sdk 12.18.0+.
For each area you use, search the listed symbols and apply the replacements:
| Area | Search for | Replace with / action |
|---|---|---|
| Functions (web/macOS) | e.message ===, error.message === on callable failures | Tolerate trailing [<digits>]; optionally read customData.url on Platform.other only |
| Messaging sound | notification.sound (string APNs sound) | notification.ios.sound |
Messaging sentTime | string/parseInt/* 1000 on sentTime (iOS) | Use numeric epoch-ms directly |
| Messaging badge | numeric use of notification.ios.badge | Parse string (Number(...)) when a number is required |
Messaging onMessageSent | ({ messageId }) / .messageId in the listener | Use the string argument directly |
| AI Imagen | getImagenModel, getTemplateImagenModel, ImagenModel, Imagen*, generateImages | getGenerativeModel / getTemplateGenerativeModel with responseModalities + imageConfig |
yarn compile
# TypeScript consumers: fix compile errors from removed Imagen exports and messaging types
# Manually exercise: callable error message handling (web/macOS), FCM notification fields (iOS), onMessageSent, Gemini image generation
Core / App