> Canonical URL: https://codingcat.dev/post/firebase-2026-03-fdc-native-sql

# Writing SQL in SQL Connect in Firebase: Web Architecture & Implementation Guide

![CodingCat.dev tutorial cover image](https://cdn.sanity.io/images/hfh83o0w/production/c2d8e2107d9120829fe3c36a2173a68be8522829-1200x630.svg?w=1200&q=82&fit=max&auto=format)[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)March 25, 2026

For years, choosing Firebase meant trading away relational SQL joins, strict foreign keys, and pgvector similarity search in favor of NoSQL document denormalization. Following the announcement of **Writing SQL in SQL Connect**  by Cynthia Wang on the [**Official Firebase Blog**](https://firebase.blog/posts/2026/03/fdc-native-sql/) (2026-03-24), here is our hands-on CodingCat.dev breakdown of what actually shipped, how it works under the hood, and how we wire it into production TypeScript, Next.js, Astro, SvelteKit, React, and Angular applications. Note: Firebase Data Connect has been renamed to SQL Connect. Learn more in the SQL Connect announcement.

## The Mental Model: Why Writing SQL in SQL Connect Matters for Web Devs

As a Google Developer Expert (GDE) for Firebase and Senior Solutions Engineer at CodingCat.dev, I evaluate every Firebase update through one lens: does it reduce boilerplate while keeping our runtime security and cloud bills predictable? Firebase Data Connect bridges that gap by pairing a managed Google Cloud SQL for PostgreSQL instance with a declarative GraphQL schema compiler that automatically generates type-safe TypeScript, React, and Angular SDKs alongside server-enforced @auth directives.

> AJ Pro-Tip from Alex: Keep your authorization logic inside your Data Connect GraphQL operations using @auth(level: USER) and server-evaluated expressions like auth.uid and request.time rather than trusting client-supplied user IDs in query variables.

## How It Works: Technical Breakdown & Implementation

Data Connect (now SQL Connect) is our comprehensive PostgreSQL solution, designed to let you build secure, scalable apps backed by Cloud SQL. Instead of writing and maintaining a traditional backend, you define your schema and operations using a declarative syntax based on GraphQL. This approach gives you strict type safety, relation resolution, and custom directives for common features like Firebase Auth right out of the box, without the hassle of having to write your own resolvers.

However, there are times when you really want the flexibility and power of SQL. When you hit advanced use cases, like window functions, geospatial indexing, or complex atomic operations, shoehorning these SQL operations into GraphQL types becomes much more difficult.

That’s why The Firebase team announced [**Native SQL for Data Connect**](https://g.dev/firebase/data-connect-native-sql-guide). It provides direct access to your database, bridging the gap between FDC’s features and raw SQL operations, so you can write the exact statements you need.

### What is native SQL?

Native SQL is a feature in Data Connect that lets you define operations using raw SQL syntax while still benefiting from Data Connect’s other features like Firebase integrations and auto-generated SDKs.

Instead of letting Data Connect generate SQL based on your operations, you can provide the SQL commands directly into your standard`.gql` operation files. Because these statements are predefined in the backend, clients cannot modify them on the fly. This allows you to execute your operations securely without having to worry about SQL injections.

### How does it work?

Native SQL is implemented via new root fields on the `query` and `mutation` types within Data Connect. You define named operations in your operation`.gql` files using these fields, passing your raw SQL string and a list of parameters. (See [**docs**](https://g.dev/firebase/data-connect-native-sql-guide) for more details).

When you run `firebase deploy --only dataconnect`, the FDC CLI compiles these operations, validates the SQL string as a static literal, and rejects forbidden commands like Data Definition Language (DDL). At runtime, the client invokes the named operation. The FDC server securely binds the parameters to the pre-deployed SQL template and executes the query against the Cloud SQL PostgreSQL database.

Used exclusively for Data Query Language (DQL). The deploy-time validation will block DML statements here.

- **_select**: Executes a query expecting zero or more rows. Returns `[Any]` (a JSON array).
- **_selectFirst**: Executes a query expecting exactly one row. Returns `Any` (a JSON object or null).

Let’s implement these concepts using a social forum app. This first query uses PostgreSQL’s `HAVING` clause to filter grouped post tags, a complex aggregation standard syntax would have difficulty expressing.

```
# Finds trending tags that have a high average view count.
query
  _select(
    sql:
      SELECT tag_name, AVG(views) as avg_views, COUNT(id) as post_count
      FROM posts
      GROUP BY tag_name
      HAVING AVG(views) >= $1
      ORDER BY avg_views DESC
    "
    params: [$minAvgViews]
  )
}
```

Used for Data Manipulation Language (DML) such as `INSERT`, `UPDATE`, and `DELETE`.

- **_execute**: Executes a DML statement returning the result count as an `Int`.
- **_executeReturning**: Executes a DML statement with a `RETURNING` clause for multiple rows, returning `[Any]`.
- **_executeReturningFirst**: Executes a DML statement returning a single row as `Any`.

This mutation bulk-updates an author’s drafts in a single command, manipulating massive amounts of data without having to fetch it to the client first.

```
# Automatically archive drafts that haven't been touched in a year.
mutation
  _execute(
    sql:
      UPDATE posts
      SET status = 'archived'
      WHERE status = 'draft' AND last_updated

Native SQL prevents SQL injection and unauthorized access by:

- **Server-controlled SQL: ** SQL statements are hardcoded in the deployed`.gql` files. The client cannot send arbitrary SQL at runtime.
- **Deploy-time validation: ** FDC validates SQL strings during deployment and rejects destructive commands.
- **Strict parameterization: ** Inputs are passed via a `params` array and bound to positional placeholders (`$1`, `$2`) by the database driver. The SQL string is never concatenated with unvetted inputs.
- **Server-side context injection: ** You can inject trusted server state into your parameters using the `_expr` directive. For example, `{_expr: "auth.uid"}` is resolved server-side to the authenticated user’s ID before execution, preventing client-side spoofing.

### What can you do with native SQL (that you couldn’t before)

- **Direct DML: ** Run `INSERT`, `UPDATE`, and `DELETE` operations without relying strictly on generated FDC mutations.
- **Multi-step execution with Common Table Expressions: ** The `_execute` field allows you to write multi-step sql using CTEs (`WITH` keyword) to chain multiple operations together.
- **Complex aggregations and window functions: ** Execute statements using `RANK()`, `PARTITION BY`, or deeply nested subqueries.
- **Flexible return structures: ** You no longer need to write boilerplate GraphQL types in `schema.gql` just to return the result of a custom aggregation or window function.
- **Postgres extensions: ** Directly query extensions like PostGIS for spatial data without mapping complex geometry types into the GraphQL schema.

For a comprehensive list, see the [**documentation**](https://g.dev/firebase/data-connect-native-sql-guide).

### Difference from @view

While native SQL provides SQL’s execution flexibility and dynamic JSON returns, there are times when you want the strict type-safety of a standard Data Connect object in addition to the ability to write complex SQL expressions.

For these scenarios, Data Connect also offers the `@view` directive, which you can use in read-only contexts. By applying `@view` to an `OBJECT` type in your `schema.gql`, you can back that specific type with a custom `SELECT` statement. This allows you to encapsulate complex database logic on the server while keeping your client-side code strictly typed.

Data Connect maps the GraphQL fields on your `@view` type to the columns returned by your SQL statement. Also, your scalar fields (written in `camelCase`) must match the SQL columns (written in `snake_case`) in your `SELECT` statement.

```
type
  SELECT
    author_id,
    COUNT(id) AS total_posts,
    SUM(views) AS total_network_views
  FROM posts
  GROUP BY author_id
"
  authorId:
  totalPosts:
  totalNetworkViews:
}
```

- **sql: ** The native SQL `SELECT` statement used as the basis for this type.
- **name: ** The name of a pre-existing SQL view in your Postgres database (cannot be used alongside the `sql` argument).
- **singular / plural: ** Configures the naming conventions in your generated API.

Because views are integrated into your schema, they carry a few specific rules that differ from native SQL:

- **Read-only: ** Only `SELECT` statements are permitted. Views cannot be mutated directly.
- **No primary keys: ** SQL views do not have primary keys, meaning direct key-based lookups (e.g., `authorStats(id: ...)`) are not supported.
- **Nullable fields: ** All fields on a `@view` type should generally be declared as nullable. If a field lacks a corresponding column in the SQL result, it will safely return null.
- **No inbound references: ** Other `@table` or `@view` types cannot use `@ref` to point to a view. (However, a view can contain a `@ref` pointing out to a base `@table`).
- **Runtime validation: ** As with native SQL, Data Connect does not parse or validate the SQL string inside a `@view` during deployment. Meaning that errors in your SQL, as well as mismatches between columns and fields will only surface at runtime, so always test your view queries.

### When to use which FDC approach

- **When to use native GraphQL: ** For standard CRUD operations, simple joins, and when you rely heavily on strict end-to-end type safety in your client application (e.g., fetching a basic profile or some list of arbitrary statistics).

```
query
  author(id: $id) {
    id
    username
    published_posts {
      title
      views
    }
    followers {
      username
    }
  }
}
```

- **When to use @view: ** When you have a stable, read-only SQL query and you want the client to have a strictly typed object for the result. A `@view` is ideal here because the author leaderboard’s SQL structure is stable and predictable.

```
type
  SELECT author_id, SUM(views) as total_network_views
  FROM posts
  GROUP BY author_id
  ORDER BY total_network_views DESC
"
  authorId:
  totalNetworkViews:
}

query
  topAuthors { authorId, totalNetworkViews }
}
```

- **When to use Native SQL: ** For complex aggregations, window functions, specialized PostgreSQL operators, or multi-step SQL using CTEs (`WITH` clauses). It’s also for any operations that don’t map cleanly to your schema or where defining the exact return type in the schema is unnecessary overhead. For example, the query below uses SQL to calculate a rolling view average on the fly using the `AVG() OVER` window function.

```
query
  _select(
    sql:
      SELECT
        id,
        views,
        AVG(views) OVER (
          PARTITION BY author_id
          ORDER BY published_at DESC
          ROWS BETWEEN 4 PRECEDING AND CURRENT ROW
        ) as rolling_avg_views
      FROM posts
      WHERE author_id = $1
      ORDER BY published_at DESC
      LIMIT 5
    "
    params: [$authorId]
  )
}
```

- **When to use native GraphQL: ** Standard inserts and updates. This is also a more direct path as FDC’s auto-generated resolvers provide a stable, boilerplate-free approach for data manipulation.

```
mutation
  post_insert(data: {
    author_id_expr
    title
    content
    status
  })
}
```

- **When to use @transaction: ** When executing atomic operations across multiple tables where the updates, inserts, and condition checks can be expressed sequentially using FDC’s generated mutations. FDC handles this multi-table transaction by chaining updates and relying on `@check` directives to authorize mutations based on query results and prevent invalid states.

```
mutation
  reader_update(id_expr:
    wallet_coins_update
  })

  author_update(id: $authorId, data: {
    earned_coins_update
  })
}
```

- **When to use Native SQL: ** Complex conditional updates, bulk data manipulation, or enforcing very specific SQL logic that goes beyond standard CRUD capabilities. While native GraphQL offers built-in authorization (like `@auth` and `@check`), Native SQL gives more flexibility to write entirely custom conditional logic in your statements with row level security. We use Native SQL here to execute a precise conditional update while still maintaining security via `{_expr: "auth.uid"}` in the `WHERE` clause, so that authors can only update their own posts.

```
mutation
  _execute(
    sql:
      UPDATE posts
      SET title = $2
      WHERE id = $1 AND author_id = $3
    "
    params: [$postId, $newTitle, {
  )
}
```

Native SQL is also required when an atomic operation requires bulk data manipulation, complex subqueries, or chaining data within the database using Common Table Expressions (CTEs) that standard mutations cannot express. The Native SQL example below atomically deletes a user and reassigns all their posts to a system account within Postgres.

```
mutation
  _execute(
    sql:
      WITH deleted_user AS (
        DELETE FROM users
        WHERE id = $1
        RETURNING id
      )
      UPDATE posts
      SET author_id = 'system-anon'
      WHERE author_id IN (SELECT id FROM deleted_user)
    "
    params: [{
  )
}
```

### Best practices

- **Client-side validation: ** Because data-returning Native SQL fields output the `Any` type (JSON), the client SDK loses response type safety. You **must ** validate the shape of the returned JSON in your client code.
- **Don’t use line comments**: Use block comments (`/* ... */`) in your SQL strings.
- **Leverage context injection: ** Always use `{_expr: "auth.uid"}` or similar context markers in your `params` array for authorization, rather than relying on the client to send user IDs.
- **Parameterize everything**: Never use string interpolation for data values. Always use positional parameters (`$1`, `$2`) linked to the `params` array.
- **Please don’t use native SQL just because you don’t like GraphQL: ** Don’t automatically replace standard FDC queries with Native SQL unless the SQL complexity strictly demands it. You will lose the out-of-the-box benefits of FDC’s automated relation resolution and strict client typing.

### What does this mean for your app?

One of the biggest shifts with Native SQL is the intentional bypass of FDC’s strong typing. Because it returns the `Any` type (JSON format), the FDC client-side SDK generator cannot infer strict TypeScript, Swift, Kotlin, etc. interfaces. You are trading compile-time safety for execution-time flexibility. It is essential to wrap your Native SQL calls in custom client-side validation logic to safely cast the raw JSON output.

Embedding complex SQL directly means your backend performance depends entirely on your query planner’s efficiency. While standard FDC resolvers optimize lookup paths predictably, Native SQL executes exactly what you dictate. It’s generally a good idea to use Cloud SQL console’s [**query insights**](https://g.dev/cloud/sql-query-insights-fdc-native-sql) (`EXPLAIN ANALYZE`) before deploying these raw queries to ensure you aren’t introducing full table scans.

## Production Web Architecture & Security Checklist

Whether we are deploying on [**Firebase App Hosting**](https://firebase.google.com/docs/app-hosting), Cloudflare Workers, or Vercel, shipping SQL features to production requires defense-in-depth across three layers:

- **Enforce Firebase App Check & Attestation: ** Protect your web endpoints, Cloud Functions, Data Connect GraphQL resolvers, and Firebase AI Logic calls against unauthorized replay or billing abuse using [**Firebase App Check with reCAPTCHA Enterprise**](https://firebase.google.com/docs/app-check).
- **Validate Locally with the Firebase Emulator Suite: ** Run deterministic integration tests against local Firestore, Auth, Functions, and Data Connect PostgreSQL emulators before promoting schema or rule changes to production.
- **Keep Server & Client Boundaries Type-Safe: ** Use the modular Firebase Web SDK and generated TypeScript bindings so SSR frameworks like Next.js, Astro, SvelteKit, and Angular serialize state cleanly without hydration flickers.

## Conclusion

Accelerate your full-stack web and AI development by pairing **Writing SQL in SQL Connect**  with strict Security Rules, Firebase App Check, and local emulator testing. Come tell us on [**X (@CodingCatDev)**](https://x.com/CodingCatDev) and [**LinkedIn (CodingCat.dev)**](https://www.linkedin.com/company/codingcatdev) how you are using Firebase in your stack, and subscribe to the [**CodingCat.dev YouTube Channel**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1) so you don't miss what we build next!

## Watch Our Related Firebase Video Tutorial on YouTube

Want to see how we architect and debug Firebase web applications live? Watch our related CodingCat.dev session [**Firebase SQL with Data Connect**](https://youtu.be/N25khadfUag) below, and make sure to [**Subscribe to CodingCat.dev on YouTube**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1) for weekly full-stack web and AI deep dives:

## Resources, Official Docs & Related CodingCat.dev Guides

- **Primary Release Source: ** [Writing SQL in SQL Connect on the Official Firebase Blog](https://firebase.blog/posts/2026/03/fdc-native-sql/) (published 2026-03-24 by Cynthia Wang)
- **Official Firebase Documentation: ** [Firebase Developer Docs & Web SDK Reference](https://firebase.google.com/docs/web/setup)
- **Related on CodingCat.dev: [From prompt to production: Build full-stack apps faster with Google AI Studio and Firebase](/post/firebase-2026-03-announcing-ai-studio-integration)**
- **Related on CodingCat.dev: [Next.js Deployment Adapters: A bright future for Next.js on Google Cloud](/post/firebase-2026-03-nextjs-adapters)**
- **Related on CodingCat.dev: [Firebase SQL with Data Connect](/podcast/firebase-sql-with-data-connect)**
- **Related on CodingCat.dev: [Build a Custom No-Code App in 90 Minutes](/podcast/build-a-custom-no-code-app-in-90-minutes)**

## Frequently Asked Questions (FAQ)

### What are the key capabilities introduced in Writing SQL in SQL Connect?

Writing SQL in SQL Connect (announced 2026-03-24 by the Firebase team) enhances the platform for Firebase, Google Cloud, Web Dev, SQL, TypeScript, Security workloads. Note: Firebase Data Connect has been renamed to SQL Connect. Learn more in the SQL Connect announcement. Developers can review the primary announcement on the [**Firebase Blog**](https://firebase.blog/posts/2026/03/fdc-native-sql/) alongside our implementation patterns and production checklist above.

### How do web developers use Writing SQL in SQL Connect with Next.js, SvelteKit, React, or Angular?

Web developers can integrate these features using the modular Firebase JavaScript/TypeScript SDK, Firebase Data Connect web bindings, and [**Firebase App Hosting**](https://firebase.google.com/docs/app-hosting). Always pair client-side SDK initialization with Firebase App Check and strict Security Rules, and test locally with the Firebase Local Emulator Suite before deploying.

### Where can I watch video tutorials on Firebase Web and AI development?

You can watch our hands-on Firebase video walkthroughs on the [**CodingCat.dev YouTube Channel**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1), including [**Firebase SQL with Data Connect**](https://youtu.be/N25khadfUag), and follow us on [**X (@CodingCatDev)**](https://x.com/CodingCatDev) for weekly full-stack tutorials.

## Recent Posts

[View More](/blog/page/1)[![PHP2Shell (CVE-2026-109618): How AI Found a Decade-Old Bug in 70% of the Web](https://cdn.sanity.io/images/hfh83o0w/production/e83065d0a4e2f841ce64d3f4aea8ddbbf07e5cb9-1376x768.jpg?w=1376&q=82&fit=max&auto=format)](/post/php2shell-cve-2026-109618-ai-php-security-scramble)

### [PHP2Shell (CVE-2026-109618): How AI Found a Decade-Old Bug in 70% of the Web](/post/php2shell-cve-2026-109618-ai-php-security-scramble)

October 22, 2026

Two critical pre-authentication RCE flaws in PHP (CVE-2026-109618 and CVE-2026-109619), uncovered by AI models from OpenAI and Google Gemini, sparked a global patching scramble across 70% of the web. Here is how PHP2Shell works and how to patch your servers.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)[![Sanity Knowledge Bases](https://cdn.sanity.io/images/hfh83o0w/production/38b0f021c51d97043c8a1ebf31d505a0ea877b7c-2752x1536.jpg?w=1920&q=82&fit=max&auto=format)](/post/sanity-knowledge-bases)

### [Sanity Knowledge Bases](/post/sanity-knowledge-bases)

October 17, 2026

Eliminate query-time guessing in your AI agents. Learn how Sanity Context Knowledge Bases compile structured datasets, crawled documentation websites, and uploaded files ahead of time, resolve conflicting facts, and serve a compact outline over Context MCP.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)