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.
This article is the companion to the main 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.
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.
Reach for Data Connect when…
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.
Reach for Firestore when…
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.
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
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.
Data Connect asks a little more of your project than Firestore does:
iOS 17 or newer
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.
The Blaze (pay-as-you-go) plan
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.
A project region — chosen once, effectively permanent
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.
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.
1
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).
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.
2
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.
3
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 for the additive-vs-destructive rules).
“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 SDKanddeploy the schema.
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.
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.
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.
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.
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. You get the same testability and token-efficiency benefits; you just don’t have to think about them.
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 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.
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.
A deploy failed with "An Internal error has occurred. Please try again." — 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.
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.
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.
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.
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.
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.”
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 → upgrade to Blaze (a credit card is required; free-tier quotas are generous), then retry the deploy.
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.
Something else is off with Firebase generally
See the Firebase guide’s troubleshooting section — 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.
Still stuck? Share a screenshot of the Firebase panel (its top status banner shows exactly which piece is out of place) in our Discord — it’s usually enough for us to pinpoint the fix.