> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modaal.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Firebase

> Connect Firebase to your iOS app — auth, cloud database, file storage, functions, analytics, and crash reporting — in a few clicks.

## What is Firebase for?

Firebase is Google's **backend-as-a-service** for mobile apps. You don't run servers, manage databases, or write auth flows from scratch — Firebase gives you all of that through a small set of SDKs.

Typical things you'd use it for:

<CardGroup cols={2}>
  <Card title="Sign-in" icon="user">
    Email+password, Apple, Google, phone (SMS code), anonymous — with one line per provider. Users stay signed in automatically across app launches.
  </Card>

  <Card title="Cloud database" icon="database">
    Save user data, share it across devices, and collaborate in real time. **Firestore** keeps a phone's local cache in sync even when offline.
  </Card>

  <Card title="SQL database" icon="table" href="/articles/firebase-data-connect">
    Need a **relational** backend — tables, joins, transactions? **Firebase Data Connect** gives you managed Postgres with a typed Swift SDK. See the dedicated guide.
  </Card>

  <Card title="File storage" icon="cloud-arrow-up">
    User avatars, attached photos, recorded audio — anything that's too big to go in the database.
  </Card>

  <Card title="Cloud Functions" icon="server">
    Server-side code you don't have to host. Run logic on signup, send push notifications, call paid APIs without exposing keys in the app.
  </Card>

  <Card title="Analytics" icon="chart-line">
    See which screens users visit, what they tap, where they drop off. Free, unlimited.
  </Card>

  <Card title="Crashlytics" icon="triangle-exclamation">
    Real crash reports from real users, grouped by cause, with stack traces and breadcrumbs. Required reading before every release.
  </Card>

  <Card title="Push notifications" icon="bell">
    Send notifications to one user, a segment, or everyone — from the Firebase console or your own backend.
  </Card>

  <Card title="Remote Config" icon="sliders">
    Flip feature flags, tune limits, A/B test copy — without shipping a new app version.
  </Card>
</CardGroup>

<Tip>
  **Need a relational database?** Firestore is a document store — it's great for document-shaped data and real-time sync, but it's not Postgres. If your data is naturally relational (strict schemas, complex joins, multi-row transactions), you don't have to leave Firebase: use **[Firebase Data Connect](/articles/firebase-data-connect)**, Google's managed Postgres backend, which Modaal sets up and manages from the same Firebase panel.
</Tip>

***

## Step-by-step setup

Open the **Firebase** tab in Modaal (the flame icon in the left sidebar of your project). The panel walks you through connecting your Google account, picking (or creating) a Firebase project, and wiring the iOS app.

<Steps>
  <Step title="Connect your Google account">
    Click **Connect**. A browser window opens; sign in with the Google account you use (or plan to use) for Firebase. Modaal stores the access token locally and refreshes it automatically in the background — you don't sign in again every day.

    <Info>
      If your Firebase projects are under a different Google account than your primary sign-in, sign in with that one. You can change accounts later from the Firebase panel's "⋯" menu.
    </Info>
  </Step>

  <Step title="Install the Firebase CLI (first project only)">
    Some operations (project creation, deployments) go through Google's `firebase` command-line tool. Modaal checks whether it's installed and offers to install it for you if not. Click **Install** — this runs `npm install -g firebase-tools` behind the scenes.
  </Step>

  <Step title="Pick or create a Firebase project">
    The **Firebase project** dropdown lists every project visible to the signed-in Google account.

    * **Reuse an existing project** (e.g., one you already created in the Firebase console for another app) by picking it from the list.
    * **Create a new one** by selecting **— Create Project —** at the top of the dropdown. Modaal creates it under your Google account using the `firebase projects:create` CLI command.
  </Step>

  <Step title="Register your iOS app">
    The panel compares your iOS bundle identifier (from `xcodegen.yml`) against the iOS apps registered in the Firebase project.

    * If there's a match, you're done — Modaal downloads `GoogleService-Info.plist`, drops it into `src-ios/App/<TargetName>/`, and wires it into the Xcode project.
    * If there's no match, click **Register this app**. Modaal registers the bundle id with Firebase and downloads the plist automatically.
  </Step>

  <Step title="(Optional) Enable deployable services">
    The **Deployable services** card lets you toggle Firestore, Cloud Storage, Cloud Functions, and Hosting. Each toggle scaffolds the local files Firebase expects (rules, TypeScript function starter, static site with AASA) and adds the matching top-level key to `firebase/firebase.json`.

    You can click **Initialize Firebase project** to turn them all on in one step.
  </Step>

  <Step title="Ask your agent to add a feature">
    From the chat, say:

    > "Add Firebase sign-in with Apple, and let users save their favorite recipes to Firestore."

    The agent reads Modaal's Firebase integration knowledge base, picks the right wrappers, writes the Swift code, and runs the build. When it needs something only a human can do — enabling a provider in the Firebase console, uploading an APNs auth key for push — it tells you exactly what to do and where.
  </Step>
</Steps>

***

## How Modaal wires Firebase — and why

Modaal doesn't drop the vanilla `firebase-ios-sdk` into your app. It adds a small Swift package called [`modaal-firebase-wrappers`](https://github.com/modaal-agent/modaal-firebase-wrappers) that wraps each Firebase SDK behind a **protocol**.

Your app depends on `ModaalFirebaseAuth`, `ModaalFirebaseFirestore`, etc. — one product per service, each re-exporting the parts of the underlying SDK you need, plus a protocol you can inject and mock.

### Why not vanilla `firebase-ios-sdk`?

<AccordionGroup>
  <Accordion title="Testability — your code no longer depends on Firebase directly">
    With the vanilla SDK, every screen that reads Firestore depends on `Firestore` directly. That means **every unit test needs a real Firestore emulator running**, or a tangled manual mock. With `ModaalFirebaseFirestore`, your screen depends on a `FirestoreServiceProtocol`. Unit tests inject a fake that returns fixed documents — no emulator, no network, milliseconds per test.

    Same story for Auth, Functions, Storage, Analytics, and Remote Config. Each one ships a protocol and a ready-made mock you can drop into test targets.
  </Accordion>

  <Accordion title="Token savings — the agent reads a small wrapper, not the whole SDK">
    When the agent writes Firebase code, it needs to know the SDK's types and method signatures. The vanilla `firebase-ios-sdk` is **over a million tokens** of symbol information across a dozen modules. The agent can't hold that in context, so it either hallucinates APIs or skips parts of the SDK.

    Modaal's wrappers are tiny — a few hundred lines per product. The agent reads the **protocols**, sees exactly what's available, and writes code that actually compiles. Fewer retries, faster turns, lower bills.
  </Accordion>

  <Accordion title="One composition root — swap the real SDK for a fake in tests or previews">
    The app builds its service dependencies once, at launch, in a single composition-root file. Swapping `FirestoreService()` (real) for `FakeFirestoreService(fixtures: ...)` (preview / UI test) is a one-line change. No conditional compilation, no feature flags, no `#if DEBUG`.

    This is the same pattern used by RIBs and clean-architecture iOS apps — the wrappers just make it easy for you without writing boilerplate.
  </Accordion>

  <Accordion title="Stable surface across Firebase SDK upgrades">
    Google bumps the Firebase SDK every few weeks. Most updates are silent, but occasionally a public type gets renamed or an enum shifts. When your code depends on `ModaalFirebaseAuth` instead of `FirebaseAuth`, the upgrade lands in the wrapper — your app code usually doesn't need to change.
  </Accordion>
</AccordionGroup>

### What Modaal puts in your project

<Expandable title="Files created during setup">
  * **`src-ios/App/<Target>/GoogleService-Info.plist`** — the per-app config file downloaded from Firebase. Added as a resource in `xcodegen.yml`.
  * **`Package.swift` / xcodegen dependency on `modaal-firebase-wrappers`** — the SPM package. You import only the products you need (e.g., `ModaalFirebaseAuth`, `ModaalFirebaseFirestore`).
  * **`firebase/firebase.json`** — the CLI config. One top-level key per enabled service.
  * **`firebase/.firebaserc`** — pins the default Firebase project id for CLI commands.
  * **`firebase/firestore.rules`**, **`firebase/storage.rules`** — security rules (start as default-deny).
  * **`firebase/functions/`** — TypeScript + Node.js starter for Cloud Functions, with `package.json`, `tsconfig.json`, example callable.
  * **`firebase/hosting/public/`** — static-site root, including the Apple App Site Association (AASA) file pre-wired with your bundle id and team id for universal links.
  * **`.modaal/firebase_deployments.json`** — Modaal-owned ledger tracking the Firebase project id, iOS app id, and content hashes of what's been deployed. Not secret; safe to commit.
  * **A `[Firebase Crashlytics] Upload dSYMs` postBuildScript** — automatically added to your Xcode target so crash reports are symbolicated without extra work.
</Expandable>

<Note>
  **GoogleService-Info.plist** is safe to commit to source control. It's not a secret — it identifies your app to Firebase, but access control lives in Firestore/Storage security rules and in provider restrictions, not in the plist itself.
</Note>

***

## Typical scenarios

### Scenario 1: Add sign-in with Apple + save favorites

You have a working app. You want users to sign in with Apple and save a list of favorites that syncs across their devices.

<Steps>
  <Step title="Open the Firebase panel and connect">
    Click the flame icon, connect your Google account, pick (or create) a Firebase project, register the iOS app. Covered above in **Step-by-step setup**.
  </Step>

  <Step title="Enable Apple sign-in in the Firebase console">
    Firebase needs Apple as a **provider** turned on. The agent can't do this — it's a privileged action in your Firebase project.

    Open [Firebase Console → Authentication → Sign-in method](https://console.firebase.google.com), enable **Apple**, and save. (The console will ask you to register a Service ID in your Apple Developer account — it walks you through it.)
  </Step>

  <Step title="Ask the agent to implement it">
    In chat:

    > "Add Apple sign-in using `ModaalFirebaseAuth`. After sign-in, save the user's uid to the `users/{uid}` document in Firestore. Add a Favorites screen that reads and writes `users/{uid}/favorites`."

    The agent will:

    * Add the `ModaalFirebaseAuth` and `ModaalFirebaseFirestore` products to `Package.swift`.
    * Add a "Sign in with Apple" button and a sign-in flow.
    * Add a `FavoritesService` that talks to Firestore through the protocol.
    * Add a real-time listener so favorites sync across devices without a refresh button.
    * Run the build and fix any errors.
  </Step>

  <Step title="Tighten security rules before you ship">
    The scaffolded `firestore.rules` is default-deny for safety. Ask the agent:

    > "Update `firestore.rules` so users can read and write their own `users/{uid}/**` subtree but nothing else."

    Click **Deploy** on the Firestore row in the Firebase panel. The rules go live in \~10 seconds.
  </Step>
</Steps>

### Scenario 2: Catch crashes in production

You're a week before launch and want to know what breaks on real devices.

<Steps>
  <Step title="Enable Crashlytics (one toggle)">
    From chat:

    > "Add `ModaalFirebaseCrashlytics` to the app. Log non-fatal errors from the top-level `catch` branches in `FavoritesService`."

    The agent adds the product, initializes it at launch, and wraps your `catch` blocks with `Crashlytics.record(error:)`. The dSYM upload script was already added when you registered the iOS app, so symbolicated reports arrive automatically.
  </Step>

  <Step title="Test the pipe before release">
    Ask:

    > "Add a hidden `crashNow()` trigger in the Settings screen that forces a crash, so I can verify Crashlytics is catching events end-to-end."

    Build, run on device, crash the app, relaunch. Within a couple of minutes, the crash appears in [Firebase Console → Crashlytics](https://console.firebase.google.com).
  </Step>

  <Step title="Remove the trigger">
    > "Remove the `crashNow()` trigger from Settings."
  </Step>
</Steps>

### Scenario 3: Send a welcome notification

You want users to receive a push notification the first time they sign in.

<Steps>
  <Step title="Enable Cloud Functions (toggle in the panel)">
    In the Firebase panel's **Deployable services** card, toggle **Cloud Functions**. Modaal scaffolds `firebase/functions/` with a TypeScript starter.

    <Warning>
      Cloud Functions require the **Blaze (pay-as-you-go) plan**. Free-tier quotas are generous, but you have to enter a credit card. Upgrade from the Firebase console if prompted.
    </Warning>
  </Step>

  <Step title="Upload an APNs auth key">
    Push notifications go through Apple Push Notification service (APNs). Open your Apple Developer account → **Keys** → create a new key with **APNs** enabled → download the `.p8` file. Upload it to [Firebase Console → Project Settings → Cloud Messaging → Apple apps](https://console.firebase.google.com).
  </Step>

  <Step title="Ask the agent to implement">
    > "Add `ModaalFirebaseMessaging` and request notification permission on first launch. When a new user is created in Firestore (`onDocumentCreated` trigger on `users/{uid}`), send them a welcome notification via FCM."

    The agent adds the iOS side (permission request, FCM token registration, foreground handler) **and** writes the Cloud Function in `firebase/functions/src/index.ts`.
  </Step>

  <Step title="Install deps and deploy">
    Run `cd firebase/functions && npm install && npm run build` (the agent can do this for you). Then click **Deploy** on the Functions row in the Firebase panel. Watch the logs stream in the modal.
  </Step>
</Steps>

***

## What do I deploy, and when?

When the agent (or you) edits a file under the `firebase/` folder, those changes are **local only** until you click the matching **Deploy** button in the Firebase panel. The live app keeps running with the last-deployed version until then.

The Firebase panel's **Deployable services** card shows one row per service with a colored dot:

* 🟢 **Green** — what's live matches your local files. Nothing to do.
* 🟡 **Yellow** — you have local changes that haven't been deployed yet. Click **Deploy** on that row.
* ⚪ **Gray** — the service is set up locally but has never been deployed. Click **Deploy** to publish it for the first time.
* 🔴 **Red** — the last deploy failed. Click **Deploy** again; the log window shows what broke.

### Changed this? Deploy that.

<Frame>
  | You edited or added…                                                            | Deploy this                                                | What happens if you skip the deploy                                                                                                                      |
  | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | **`firebase/firestore.rules`** — who can read/write your database               | **Firestore rules**                                        | The database keeps enforcing the old rules. Users can't reach new data, or can reach data you meant to lock down.                                        |
  | **`firebase/firestore.indexes.json`** — added a new query in the app            | **Firestore indexes**                                      | New queries silently fail in the app with a "query requires an index" error.                                                                             |
  | **`firebase/storage.rules`** — who can upload/download files                    | **Storage rules**                                          | Same as Firestore rules — old rules stay in effect.                                                                                                      |
  | **Anything inside `firebase/functions/`** — your server-side code               | **Cloud Functions**                                        | Clients keep calling the old version of your function until you redeploy.                                                                                |
  | **Added an npm package** in `firebase/functions/package.json`                   | **Cloud Functions** (after `npm install && npm run build`) | The function crashes at cold-start because the package isn't in the deployed bundle.                                                                     |
  | **Anything in `firebase/hosting/public/`** — your static site                   | **Hosting**                                                | The public site keeps serving the last-deployed content.                                                                                                 |
  | **The AASA file** (`apple-app-site-association`) for universal links            | **Hosting**                                                | iOS downloads the AASA from your live site, not from your computer. Until you redeploy, universal links behave however the previous AASA said.           |
  | **Changed `DEVELOPMENT_TEAM`** in iOS Project Settings after Hosting was set up | **Hosting**                                                | Modaal automatically rewrites the AASA locally, but universal links won't work with the new team id until the Hosting deploy publishes the updated file. |
</Frame>

### You do NOT need to deploy after:

* **Swift code changes** in your iOS app — those go live when you run or ship the iOS app, not through Firebase deploy.
* **Swapping the Google account connected to Modaal** — that's just a sign-in change.
* **Turning Firebase Analytics events on/off in code** — events go live with the next iOS build.
* **Enabling Apple / Google / phone sign-in** — that's done in the [Firebase Console](https://console.firebase.google.com) → Authentication → Sign-in method, not via Deploy.
* **Uploading an APNs push key** — done in the Firebase Console, not via Deploy.
* **Editing Remote Config values** — done in the Firebase Console → Remote Config (click Publish there).

<Tip>
  **Not sure what's pending?** Look at the dots in the Firebase panel. Any yellow, gray, or red dot is something worth deploying. The agent also tends to end its turn with a sentence like *"Now open the Firebase panel → Deploy on the Firestore rules row"* — that's your cue.
</Tip>

***

## Troubleshooting

When something goes wrong with Firebase, the best first move is almost always:

<Card title="Ask the agent to fix it" icon="wand-magic-sparkles">
  Type: **"Check the Firebase setup for this project and fix anything that's broken."**

  The agent walks through every place Firebase wiring lives — the project settings file, the iOS build configuration, the `GoogleService-Info.plist`, the wrappers package, the crash-reporting upload step — and fixes whatever's out of place. For anything that can only be done by a human (like enabling a provider in the Firebase console or upgrading the billing plan), it tells you the exact button to click or URL to open.
</Card>

Modaal also surfaces two ready-made prompts from the Firebase panel's "⋯" menu — click one and the agent does the rest:

* **Fix Firebase integration** — brings your project's local files back in sync with Firebase (re-downloads the plist, re-links it in the iOS project, re-adds the crash-reporting upload step).
* **Migrate to wrappers** — if your project still uses the vanilla Firebase SDK directly, this switches it over to the Modaal wrappers without changing how the app behaves.

### Common issues

<AccordionGroup>
  <Accordion title="The top banner says Firebase is not connected, even though I just signed in">
    Google access tokens expire after about an hour. Modaal refreshes them automatically in the background when the panel opens. If the refresh fails because of a transient network issue, the banner clears on the next Refresh click. If it persists, click **Connect** again — a re-auth takes ten seconds and doesn't disturb any other Firebase state.
  </Accordion>

  <Accordion title="'You don't have access to project <name>' error">
    The Google account you're signed in with isn't a member of that Firebase project. This usually means the project was created under a **different** Google account than the one you used to sign in.

    Options:

    * Click **Sign in with a different Google account** in the error banner and use the account that owns the project.
    * Or ask the project owner to invite you as a member in Firebase Console → Project settings → Users and permissions.
  </Accordion>

  <Accordion title="'Firebase CLI not found' or 'firebase login required'">
    Some operations (creating a project, deploying rules or functions) use Google's `firebase` command-line tool, which has its own separate sign-in.

    Modaal shows a **Sign in via Terminal** button when this is needed — click it, a terminal opens with `firebase login` pre-typed, finish the browser flow, click **Retry**. You only do this once per machine.
  </Accordion>

  <Accordion title="'Firebase Storage has not been set up' when I click Deploy">
    Firebase Storage needs a one-time console bootstrap. Open [Firebase Console → Storage](https://console.firebase.google.com) and click **Get started**. Come back to Modaal and click Deploy again.
  </Accordion>

  <Accordion title="Cloud Functions deploy fails with 'project must be on the Blaze plan'">
    Cloud Functions require Firebase's pay-as-you-go plan (free-tier quotas are generous, but a credit card is required). Click the URL in the error to upgrade, then retry the deploy.
  </Accordion>

  <Accordion title="Crashes don't appear in Crashlytics">
    Checklist:

    * Did the app crash on a **real device**, not the Simulator? (Simulator crashes don't upload.)
    * Did you **relaunch** the app after the crash? Crashlytics uploads on the next launch, not at crash time.
    * Is the `[Firebase Crashlytics] Upload dSYMs` post-build script present in your Xcode target? (The Firebase panel's status banner shows this explicitly. If it's missing, click **Fix Firebase integration**.) This script turns raw crash addresses into readable function names — without it, reports show up but you can't tell where the crash happened.
  </Accordion>

  <Accordion title="Apple Universal Links / AASA file has the wrong team id">
    The **AASA file** at `firebase/hosting/public/.well-known/apple-app-site-association` is what tells iOS to open your app when someone taps a link to your site. It's generated when Hosting is first enabled, using whatever Apple Team was selected at that moment. If you change the team later from the iOS Project Settings panel, Modaal automatically rewrites the AASA file to match. If you edited it by hand before changing the team, re-run **Deploy** on the Hosting row to publish the refreshed file.
  </Accordion>

  <Accordion title="I created the Firebase project in the console, but Modaal doesn't see it in the dropdown">
    Click the refresh icon (↻) at the top of the Firebase panel to re-fetch the list of projects. If the project still doesn't appear, the Google account signed in to Modaal probably isn't the one that created it — re-check which account you used in the Firebase console.
  </Accordion>

  <Accordion title="I need to roll Firebase back / start over">
    From the Firebase panel's "⋯" menu, pick **Disconnect**. This clears the stored Google token but **does not** touch files in your repository (`GoogleService-Info.plist`, `firebase/…`, Package.swift, etc.) — your app keeps working and can be re-connected to the same or a different Firebase project later.

    To remove Firebase from the project entirely, ask the agent:

    > "Remove all Firebase integration from this project — the wrappers package, the plist, the post-build script, and the `firebase/` directory."
  </Accordion>
</AccordionGroup>

<Tip>
  **Still stuck?** Share a screenshot of the Firebase panel (the top status banner shows exactly which piece of the setup is out of place) in our [Discord](https://discord.gg/KyQzDXxgU3) — it's usually enough for us to pinpoint the fix.
</Tip>
