Migrating to v27

Migrate to React Native Firebase v27 — Firebase JS SDK 12.18.0, messaging corrections, and Imagen API removal.

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.message appends an HTTP status suffix and customData.url is 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/ai after Google shut down the Imagen models as a service on 2026-08-17. Generate images with a Gemini image model through getGenerativeModel() instead.

If you are upgrading from v25 or earlier, complete Migrating to v26 first — the namespaced API removal and New Architecture requirement still apply.

Table of contents

Agent-assisted migration

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.

Firebase JS SDK 12.18.0

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):

ChangeBeforeAfter (v27 / JS SDK 12.18.0)Who
FunctionsError.messageExact server message stringMessage plus a trailing [<httpStatus>] suffix (for example Invalid test requested. [400])Web / macOS only
FunctionsError.customData.urlNot presentRequest URL string on customDataWeb / 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.

Migration actions

  1. Search for exact-equality checks on error.message / e.message for callable failures.
  2. On web/macOS, prefer message.startsWith(expected) or strip a trailing [digits] before comparing.
  3. If you need the HTTP status or request URL, read them from the suffix / customData.url on web/macOS only; keep a native fallback path for iOS/Android.
js
// 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?.url

Upstream note: firebase@12.18.0 — “Add HTTP status code to the message field and add url to customData.”

Cloud Messaging

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.

String APNs sound → notification.ios.sound

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)
js
// Previously
const sound = message.notification?.sound;

// Now
const sound = message.notification?.ios?.sound;

iOS sentTime is epoch milliseconds

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.

js
// Previously (iOS)
const sentMs = Number(message.sentTime) * 1000;

// Now (iOS and Android)
const sentMs = message.sentTime; // number, epoch ms

iOS badge is a string

PR: #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")
js
// Previously
const badge = message.notification?.ios?.badge; // number on iOS

// Now
const badge = Number(message.notification?.ios?.badge ?? 0);

onMessageSent receives a string message ID

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
js
// Previously
onMessageSent(messaging, ({ messageId }) => {
  console.log(messageId);
});

// Now
onMessageSent(messaging, messageId => {
  console.log(messageId);
});

Removed Imagen API

Nothing else in @react-native-firebase/ai changed. getGenerativeModel(), getTemplateGenerativeModel(), TemplateGenerativeModel, and Gemini image generation are unaffected.

Removed exports

{/* prettier-ignore */}

RemovedReplacement
getImagenModelgetGenerativeModel() with a Gemini image model
getTemplateImagenModelgetTemplateGenerativeModel() with a template that uses an image model
ImagenModelGenerativeModel
TemplateImagenModelTemplateGenerativeModel
ImagenModelParamsModelParams
ImagenGenerationConfigGenerationConfig with responseModalities and imageConfig
ImagenImageFormatNo replacement — Gemini image models always return PNG; read inlineData.mimeType to confirm
ImagenGenerationResponseGenerateContentResult
ImagenInlineImageInlineDataPart on the response candidate
ImagenGCSImageNo replacement — Gemini image models return inline data
ImagenAspectRatioImageConfigAspectRatio
ImagenSafetySettingsSafetySetting[] on getGenerativeModel()
ImagenSafetyFilterLevelHarmBlockThreshold
ImagenPersonFilterLevelNo replacement — Gemini image models allow generating people by default

Example

js
// 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);
js
// 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.

Automated migration checklist

Use this section when running scripted or agent-assisted upgrades from v26 → v27.

1. Upgrade dependencies

bash
# 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 install

Confirm the lockfiles resolve firebase / firebase-js-sdk 12.18.0+.

2. Agent-oriented search and replace

For each area you use, search the listed symbols and apply the replacements:

AreaSearch forReplace with / action
Functions (web/macOS)e.message ===, error.message === on callable failuresTolerate trailing [<digits>]; optionally read customData.url on Platform.other only
Messaging soundnotification.sound (string APNs sound)notification.ios.sound
Messaging sentTimestring/parseInt/* 1000 on sentTime (iOS)Use numeric epoch-ms directly
Messaging badgenumeric use of notification.ios.badgeParse string (Number(...)) when a number is required
Messaging onMessageSent({ messageId }) / .messageId in the listenerUse the string argument directly
AI ImagengetImagenModel, getTemplateImagenModel, ImagenModel, Imagen*, generateImagesgetGenerativeModel / getTemplateGenerativeModel with responseModalities + imageConfig

3. Validate

bash
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

Related PRs (v27 breaking changes)

AreaPR
Firebase JS SDK 12.18.0 / FunctionsError#9218
Messaging — APNs string sound path#9246
Messaging — iOS sentTime epoch-ms#9247
Messaging — iOS badge string#9252
Messaging — onMessageSent string ID#9254
AI — Imagen API removal#9275