> ## 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 Data Connect

> Add a real SQL (Postgres) backend to your iOS app — relational tables, joins, transactions, and a typed Swift SDK — managed from Modaal's Firebase panel.

Firebase **Data Connect** is Google's SQL backend for Firebase: a fully-managed **PostgreSQL** database (running on Cloud SQL) that you model with a GraphQL schema and query with a **typed Swift SDK** generated for you. It sits next to the rest of Firebase — same project, same sign-in, same billing — and coexists happily with Firestore in the same app.

Modaal manages the whole lifecycle from the **Firebase panel**: scaffold the service, generate the typed Swift SDK, and deploy the schema — no Terminal, no hand-written GraphQL plumbing. Your agent writes the schema and the Swift data layer; you click the buttons that talk to Google.

<Note>
  This article is the companion to the main **[Firebase](/articles/firebase)** guide. Everything there — connecting your Google account, installing the CLI, the Deploy panel, Crashlytics, sign-in — still applies. This page covers only the SQL/Data Connect piece.
</Note>

## Data Connect vs Firestore — which one?

Both are databases in the same Firebase project, but they solve different shapes of problem. Firestore stores **documents**; Data Connect stores **rows in relational tables**.

<CardGroup cols={2}>
  <Card title="Reach for Data Connect when…" icon="diagram-project">
    Your data is **relational** — separate `User`, `Friendship`, `Post`, `Order` tables joined by foreign keys. You need **joins**, **server-side aggregation**, or **transactions** that write several rows atomically. You want a **typed query surface** the compiler checks.
  </Card>

  <Card title="Reach for Firestore when…" icon="file-lines">
    Your data is **document-shaped** — self-contained or tree-shaped records. **Offline-first** reads and writes are a product requirement. Per-document writes dominate, and you're fine with weakly-typed dynamic field access.
  </Card>
</CardGroup>

<Frame>
  | Pick **Data Connect** when…                                               | Pick **Firestore** when…                                                      |
  | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
  | The schema has foreign keys, many-to-many tables, or joins are common     | Documents are self-contained or naturally tree-shaped                         |
  | Server-side aggregations or multi-row transactional writes are required   | Per-document writes are the dominant pattern                                  |
  | The model is naturally normalized (`User` / `Friendship` / `Post` tables) | The model is naturally denormalized (a user doc with an embedded friend list) |
  | You want a typed Swift API generated from a schema                        | You're OK with dynamic, untyped field access                                  |
  | Offline-first is **not** a hard requirement                               | Offline reads/writes are a product requirement                                |
</Frame>

<Tip>
  **If both fit, default to Firestore** — its offline cache, security rules, and indexes are the more mature story. Choose Data Connect when the schema *genuinely* needs relational semantics and you can accept the requirements below. You can also use **both in the same app** — see [Using both together](#using-both-together).
</Tip>

## Before you start — three requirements

Data Connect asks a little more of your project than Firestore does:

<AccordionGroup>
  <Accordion title="iOS 17 or newer" icon="mobile">
    The generated Swift SDK is built on Apple's **Observation framework** (`@Observable`), which is iOS 17-only. Modaal raises your app's deployment floor to iOS 17 automatically when it wires the SDK in. If your app must ship to iOS 16 or earlier, Data Connect isn't an option — use Firestore.
  </Accordion>

  <Accordion title="The Blaze (pay-as-you-go) plan" icon="credit-card">
    Data Connect provisions a **Cloud SQL Postgres instance**, which is paid infrastructure — so the Firebase project must be on the **Blaze** plan (a credit card on file). New services start on the SQL Connect no-cost trial, but billing must be enabled. If it isn't, the first deploy tells you in the log.
  </Accordion>

  <Accordion title="A project region — chosen once, effectively permanent" icon="earth-europe">
    Every Data Connect action is gated on a **region** being set (the panel's **Region** card). The region fixes where your Cloud SQL database physically lives, so it's **permanent** — changing it later means migrating the whole database. It also carries **data-residency / GDPR** weight: if you store EU users' personal data, pick an EU region (`europe-west1`, `europe-west3`, …) so the data stays in the EU. The Data Connect buttons stay disabled with a *"Set the project region first"* hint until you've chosen one.
  </Accordion>
</AccordionGroup>

## The three panel actions

Data Connect lives in the **Firebase** panel (the flame icon in the left sidebar). Once a region is set, three actions drive everything — and, exactly like every other Firebase operation, each one **streams its logs into a modal** and runs against Google's servers for you. The agent points you at the button; it never shells out to the `firebase` CLI itself.

<Steps>
  <Step title="Set up — scaffold the service">
    On a project with no Data Connect yet, the **Add a service** card shows a **Set up** button. Click it and Modaal scaffolds `firebase/dataconnect/` — a `dataconnect.yaml`, an (empty, comment-only) `schema/schema.gql`, and a `connector/` with `queries.gql` + `mutations.gql` — deriving sensible defaults from your project name (service id, Cloud SQL instance id, and the Swift package name, e.g. package `MyAppConnector`, service `myapp`).

    <Info>
      An **empty schema is valid**. The scaffolded comment-only `schema.gql` compiles and deploys fine — it provisions the service and the Cloud SQL instance with no tables. Add tables when you're ready; there's no "you must define something first" step.
    </Info>
  </Step>

  <Step title="Generate SDK — produce the typed Swift package">
    The **Data Connect** row has a **Generate SDK** button (it reads **Regenerate SDK** once the schema has changed). It runs Google's code generator server-side, commits the typed Swift package under `src-ios/Libraries/<Package>/`, and **auto-wires** it into your app's main Swift package (raising the iOS-17 floor). You don't touch `Package.swift` or `xcodegen.yml` — the panel does it.
  </Step>

  <Step title="Deploy — push the schema live">
    Schema deploys ride the **shared Deploy button** below the service rows — Data Connect is just another target there, alongside Firestore, Storage, Functions, and Hosting. When you've edited the schema, the Data Connect row shows a **Schema changed** badge and the Deploy button includes it. Deploying applies the change to the live Postgres database (see [Deploying schema changes](#deploying-schema-changes) for the additive-vs-destructive rules).
  </Step>
</Steps>

<Warning>
  **"Regenerate SDK" and "Deploy" are two different things — and a schema edit usually needs both.** The **SDK stale** badge means your *local* typed Swift package is behind your `.gql` files → click **Regenerate SDK**. The **Schema changed** badge means the *live database* is behind your local schema → click **Deploy**. They drift independently. After editing the schema you almost always want to *regenerate the SDK* **and** *deploy the schema*.
</Warning>

The Data Connect row also shows a one-line status — the service id, package name, and region — plus an aggregate badge: **Not generated**, **Not wired**, **Wired**, **SDK stale**, or **Schema changed**.

## What Modaal puts in your project

<Expandable title="Files created for Data Connect">
  * **`firebase/dataconnect/dataconnect.yaml`** — the service config: service id, region, and the Cloud SQL instance id. The service id and instance id are **frozen after the first deploy** (renaming a live service is a data migration, not an edit).
  * **`firebase/dataconnect/schema/schema.gql`** — your tables, written in Data Connect's GraphQL dialect (`@table`, `@ref`, `@index`, `@auth`, …). Starts empty.
  * **`firebase/dataconnect/connector/queries.gql`** and **`mutations.gql`** — your read and write operations. Split by convention; the code generator turns each one into a typed Swift call.
  * **`src-ios/Libraries/<AppName>Connector/`** — the **generated, committed** Swift package. It's checked into source control on purpose, so teammates can build without installing the Firebase CLI. Don't hand-edit it — it's overwritten on every regenerate.
  * **A `dataconnect` block in `firebase/firebase.json`** — so `firebase deploy` picks the service up.
</Expandable>

<Note>
  The generated Swift package is **committed source**, not build output. That keeps builds deterministic and means only the person editing the schema needs the Firebase CLI. When you (or the agent) change the schema, regenerate and commit the refreshed package.
</Note>

## How the agent builds with it

You don't write GraphQL or wire up Swift generics by hand. Ask the agent, and it reads Modaal's Data Connect knowledge base and does the modeling for you:

> "Model a friends feature in Data Connect: a `User` table keyed by the Firebase Auth uid, a `Friendship` join table with an `isActive` flag, and a `Memory` table each user can share with several friends. Add queries for my friends list and my incoming feed."

The agent will:

* Write the tables and relationships in `schema/schema.gql`, and the read/write operations in `queries.gql` / `mutations.gql`.
* Tell you to click **Regenerate SDK** and **Deploy** in the panel (only you can push to Google).
* Write the iOS data layer behind a **per-feature repository protocol** — one clean protocol your screens depend on, with the generated SDK hidden inside a single implementation file. That keeps your views testable (they inject a fake repository) and swappable (Data Connect could be replaced without touching feature code).
* Keep server-only operations (privileged cross-user reads, account-deletion cascades, invite redemption) **out of the shipped client SDK** by putting them in a separate connector, so they never appear as callable symbols in your app binary.

<Tip>
  The architecture the agent follows — protocol-per-feature, a domain type mirrored at the boundary, Combine and async variants side by side — mirrors the Modaal Firebase **wrappers** pattern described in the [Firebase guide](/articles/firebase#how-modaal-wires-firebase-and-why). You get the same testability and token-efficiency benefits; you just don't have to think about them.
</Tip>

## Live queries

Data Connect queries can be **observed** — the UI updates when the underlying rows change — the same way Firestore listeners work. The agent bridges the generated observable query into your app's Combine or SwiftUI layer and cleans up the subscription when the view goes away. Refreshes can be **automatic** (a query keyed by primary key refreshes when a matching write executes) or **time-based** (a periodic backstop that catches changes made on another device). You don't manage any of this directly — you ask for "a friends list that stays in sync across devices" and the agent picks the right refresh strategy.

## Deploying schema changes

Deploying Data Connect is different from deploying rules or functions, because **the database has data in it**. Two kinds of migration:

* **Additive** — a new table, a new nullable column, a new index. These apply **automatically**, no prompt.
* **Destructive** — dropping a column, narrowing a type, removing a `NOT NULL` constraint. These are **blocked**: the deploy stops, the modal shows you the Postgres migration plan (the exact SQL), and an explicit **Apply destructive migration** button is the only way to proceed.

<Warning>
  **Read the SQL diff before you click Apply destructive migration.** A destructive migration runs with `--force` and can **permanently delete data** — it cannot be undone. The deploy log shows precisely what will be dropped. Never click it blind, and never let the agent tell you to click it without showing you the diff first.
</Warning>

<Accordion title="A deploy failed with &#x22;An Internal error has occurred. Please try again.&#x22; — should I retry?">
  **Almost never.** When this opaque error appears right after the log's `ensuring required API … sqladmin.googleapis.com` lines, it's usually **not** a flaky backend — it's a *breaking change* (a destructive SQL migration, or a query field that changed from required to optional) that the non-interactive deploy can't prompt you to confirm. Retrying the same deploy just loops.

  Modaal detects this and surfaces a **Data Connect deploy blocked** gate with two ways forward: **Deploy with `--force`** (after you've read the diff in the log), or **Open in Terminal** (you run the deploy yourself and answer the breaking-change prompt interactively). When in doubt, use **Open in Terminal** so you can review it live.
</Accordion>

<Note>
  **The very first deploy provisions everything and takes several minutes.** It enables the required Google APIs and creates the Cloud SQL Postgres instance in your chosen region. The schema migration is skipped while the instance is still spinning up (that's expected, not an error) — the deploy still completes, and the *next* deploy applies your schema once the instance is live.
</Note>

## Using both together

Data Connect and Firestore coexist in one app with no special wiring — Modaal's Firebase SDK resolves to a single shared Firebase core, which the generated Data Connect package uses too. A common split:

* **Firestore** — user profile documents, settings, chat messages, anything document-shaped or offline-first.
* **Data Connect** — the relational core (posts ↔ authors ↔ tags, friendships, orders ↔ items), anything that benefits from joins or transactional consistency.

Your screens depend on whichever repository fits the data; the two never reach across each other. Ask the agent to "keep profiles in Firestore but move the social graph to Data Connect" and it wires both.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The Data Connect buttons are greyed out">
    You haven't set a **region** yet. Every Data Connect action (Set up, Generate SDK, and the shared Deploy) is gated on it. Open the **Region** card at the top of the Firebase panel and pick one — remember it's effectively permanent and has data-residency implications, so choose an EU region if you store EU personal data.
  </Accordion>

  <Accordion title="The app crashes decoding a Data Connect response after a schema change">
    Your generated SDK is **stale** — the schema (and the live server) moved on, but the local typed package still encodes the old operation shapes. The row shows an **SDK stale** badge. Click **Regenerate SDK** on the Data Connect row and rebuild. Always regenerate after every `.gql` edit.
  </Accordion>

  <Accordion title="Build error: cannot find type 'Observable' in scope">
    The app is being built against **iOS 16 or earlier**. The generated SDK needs iOS 17 (it uses the Observation framework). Modaal raises the floor when it wires the SDK in — if you see this, check that your app target's deployment target is 17.0 and rebuild. Ask the agent to "raise the iOS deployment target to 17 everywhere Data Connect needs it."
  </Accordion>

  <Accordion title="Deploy fails mentioning the Blaze plan or billing">
    Data Connect needs a Cloud SQL instance, which is paid infrastructure — the Firebase project must be on the **Blaze** plan. Open the [Firebase Console](https://console.firebase.google.com) → upgrade to Blaze (a credit card is required; free-tier quotas are generous), then retry the deploy.
  </Accordion>

  <Accordion title="I opened a project that already had a firebase/dataconnect/ folder">
    Modaal reconciles the on-disk config against its own records through the **top-level Firebase banner** (the wiring-health surface). If it's missing metadata it can backfill, the **Fix Firebase wiring** button does it (strictly additive — it never overwrites). If the on-disk ids *disagree* with what Modaal recorded (e.g. a renamed service), it routes you to a **Have the agent fix it** prompt — treat that as a reconciliation, not a silent rewrite, because a deployed service's id and Cloud SQL instance are frozen.
  </Accordion>

  <Accordion title="Something else is off with Firebase generally">
    See the **[Firebase guide's troubleshooting section](/articles/firebase#troubleshooting)** — account/token issues, the CLI login handoff, and the general "ask the agent to fix the setup" move all live there and apply here too.
  </Accordion>
</AccordionGroup>

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