Implement Data Connect queries Stay organized with collections Save and categorize content based on your preferences.
Firebase Data Connect lets you create connectors for your PostgreSQLinstances managed with Google Cloud SQL. These connectors are combinations of aqueries and mutations for using your data from your schema.
Note: Follow the complete series on building Data Connect schemas and connectors, which covers:
- Schema development
- Query development (this guide)
- Mutation development
TheGet started guide introduced a moviereview app schema for PostgreSQL.
That guide also introduced both deployable and ad hoc administrativeoperations, including queries.
- Deployable queries are those you implement to call from clientapps, with API endpoints you define. You bundle them into aconnectordeployed to the server.Data Connect tooling generates client SDKsbased on your API. Deployed queries aren't protected by IAM policy, so besure to secure them using theData Connect
@authdirective. - Ad hoc administrative queries are run from privileged environments toread data. You can create and execute them in theFirebase console or inlocal development environments using our Data Connect VS Code extension.
This guide takes a deeper look atdeployable queries.
Features ofData Connect queries
Data Connect lets you perform basic queries in all the waysyou'd expect given a PostgreSQL database.
But withData Connect's extensions to GraphQL, you can implementadvanced queries for faster, more efficient apps:
- Usekey scalars returned by many operations to simplify repeatedoperations on records
- Perform queries in the course of a multi-step mutation operations to look updata, saving lines of code and round trips to the server.
Movie review app schema
typeUser@table{id:String!@default(expr:"auth.uid")username:String!@col(dataType:"varchar(50)")}typeReview@table(name:"Reviews",key:["movie","user"]){user:User!movie:Movie!rating:IntreviewText:StringreviewDate:Date!@default(expr:"request.time")}typeMovie@table(name:"Movies",singular:"movie",plural:"movies",key:["id"]){id:UUID!@col(name:"movie_id")@default(expr:"uuidV4()")title:String!releaseYear:Int@col(name:"release_year")genre:Stringrating:Int@col(name:"rating")description:String@col(name:"description")tags:[String]@col(name:"tags")}typeMovieMetadata@table(name:"MovieMetadata"){movie:Movie!@refdirector:String@col(name:"director")}extendtypeMovie{movieMetadata:MovieMetadatamovieMetadatas_on_movie:MovieMetadata}typeMovieActors@table(key:["movie","actor"]){movie:Movie!actor:Actor!}extendtypeMovieActors{movieId:UUID!actorId:UUID!}extendtypeMovie{movieActors:[MovieActors!]!actors:[Actor!]!actors_via_MovieActors:[Actor!]!}extendtypeActor{movieActors:[MovieActors!]!movies:[Movie!]!movies_via_MovieActors:[Movie!]!}
Use generated fields to build queries
YourData Connect operations will extend a set of fieldsautomatically generated byData Connect based on the types and typerelationships in your schema. These fields are generated by local toolingwhenever you edit your schema.
Tip: To discover the generated fields while building operations, use the queryeditor in theFirebase console, or our Visual Studio Code extension.You can use generated fields to implement increasingly complex queries, fromretrieving individual records or multiple records from single tables to multiplerecords from related tables.Assume your schema contains aMovie type and an associatedActor type.Data Connect generatesmovie,movies,actors_on_movies fields, and more.
Movie_Key, to identify records. These are created based on yourschema.Query with the
movie field
The | Use this field to query a single movie by its key. queryGetMovie($myKey:Movie_Key!){movie(key:$myKey){title}} |
Query with the
movies field
The | Use this field to query multiple movies, for example, all movies with a given year. queryGetMovies($myYear:Int!){movies(where:{year:{eq:$myYear}}){title}} |
Query with the
actors_on_movies field
The | Use this field to query all actors associated with a given movie. queryGetActorsOnMovie($myKey:Movie_Key!){actors_on_movies(where:{movie:{key:{eq:$myKey}}}){actor{name}}} |
singular andplural arguments for the@table directive.Essential elements of a query
Data Connect queries are GraphQL queries with Data Connect extensions. Justas with a regular GraphQL query, you can define an operation name and a listof GraphQL variables.
Data Connect extends GraphQL queries with customizeddirectives like@auth.
So the following query has:
- A
querytype definition - A
ListMoviesByGenreoperation (query) name - A single query argument, here a
$genrevariable ofStringtype - A single directive,
@auth. - A single field,
movies.
queryListMoviesByGenre($genre:String!)@auth(level:PUBLIC){movies(where:{genre:{eq:$genre}}){idtitle}}Every query argument requires a type declaration, a built-in likeString, or acustom, schema-defined type likeMovie.
This guide will look at the signature of increasingly complex queries. You'llend by introducing powerful, concise relationship expressions you can use tobuild your deployable queries.
Key scalars in queries
But first, a note about key scalars.
Data Connect defines a specialkey scalar to represent primarykeys of each table, identified by {TableType}_Key. It is a JSON object ofprimary key values.
You retrieve key scalars as a response returned by most auto-generated readfields, or of course from queries where you have retrieved all the fieldsneeded to build the scalar key.
Singular automatic queries, likemovie in our running example, support a keyargument that accepts a key scalar.
You might pass a key scalar as a literal. But, you can define variables to passkey scalars as input.
Query
queryGetMovie($myKey:Movie_Key!){movie(key:$myKey){title}}
Response
{"data":{"movie":{"title":"Example Movie Title"}}}
These can be provided in request JSON like this (or other serializationformats):
{#…"variables":{"myKey":{"id":"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"}}}Thanks to custom scalar parsing, aMovie_Key can also be constructed using theobject syntax, which may contain variables. This is mostly useful when you wantto break individual components into different variables for some reason.
Write basic queries
You can start writing queries to get individual records from your database, orlist records from a table with the option to limit and order results.
Tip: You can use Gemini inFirebase to help you create and run GraphQL queries in theFirebase console. Learn more atUse AI assistance for queries and mutations.Retrieve individual records
The simplest query gets a single record by ID. Your query will use theauto-generatedmovie field.
Query
queryGetMovieById($id:UUID!)@auth(level:PUBLIC){movie(id:$id){idtitleimageUrlgenre}}
Response
{"data":{"movie":{"id":"some-uuid","title":"Example Movie Title","imageUrl":"https://example.com/movie.jpg","genre":"Action"}}}
Retrieve all records in a table
To retrieve a subset of fields for the full list of movies from theMoviestable, your query will make use of the auto-generatedmovies field, and yourimplementation might look like the following.
Query
queryListMovies@auth(level:PUBLIC){movies{idtitleimageUrlgenre}}
Response
{"data":{"movies":[{"id":"some-uuid","title":"Example Movie Title","imageUrl":"https://example.com/movie.jpg","genre":"Action"},{"id":"another-uuid","title":"Another Movie Title","imageUrl":"https://example.com/another-movie.jpg","genre":"Comedy"}]}}
UseorderBy,limit andoffset operators
Naturally, listing all records from a table has limited usefulness.
You can order and perform pagination on results. These arguments are acceptedbut not returned in results.
Here, the query gets the titles of the top 10 movies by rating.
Query
queryMoviesTop10{movies(orderBy:[{rating:DESC}],limit:10){# graphql: list the fields from the results to returntitle}}
Response
{"data":{"movies":[{"title":"Top Movie 1"},{"title":"Top Movie 2"},{"title":"Top Movie 3"}// ... other 7 movies]}}
You might have a use case for fetching rows from an offset, like movies 11-20ordered by rating.
Query
queryMovies11to20{movies(orderBy:[{rating:DESC}],limit:10,offset:10){# graphql: list the fields from the results to returntitle}}
Response
{"data":{"movies":[{"title":"Movie 11"},{"title":"Movie 12"},{"title":"Movie 13"}// ... other 7 movies]}}
Use aliases in queries
Data Connect supports GraphQL aliasing in queries. With aliases, yourename the data that is returned in a query's results. A singleData Connect query can apply multiple filters or other queryoperations in one efficient request to the server, effectively issuing several"sub-queries" at once. To avoid name collisions in the returned dataset, youuse aliases to distinguish the sub-queries.
Here is a query where an expression uses the aliasesmostPopular andleastPopular.
Query
queryReviewPopularitySpread($genre:String){mostPopular:review(first:{where:{genre:{eq:$genre}},orderBy:{popularity:DESC}}),leastPopular:review(last:{where:{genre:{eq:$genre}},orderBy:{popularity:DESC}})}
Response
{"data":{"mostPopular":[{"popularity":9}],"leastPopular":[{"popularity":1}]}}
Use query filters
Data Connect queries map to all common SQL filters and orderoperations.
Filter withwhere withorderBy operators
Returns all matched rows from the table (and nested associations). Returns anempty array if no records match the filter.
Query
queryMovieByTopRating($genre:String){mostPopular:movies(where:{genre:{eq:$genre}},orderBy:{rating:DESC}){# graphql: list the fields from the results to returnidtitlegenredescription}}
Response
{"data":{"mostPopular":[{"id":"some-uuid","title":"Example Movie Title","genre":"Action","description":"A great movie"}]}}
Filter by testing for null values
You can test fornull values using theisNull operator.
Query
queryListMoviesWithoutDescription{movies(where:{description:{isNull:true}}){idtitle}}
Response
{"data":{"movies":[{"id":"some-uuid","title":"Example Movie Title"},{"id":"another-uuid","title":"Another Movie Title"}]}}
For more operators, see theinput objects types reference guide.
Filter with value comparisons
You can use operators likelt (less than) andge (greater than or equal)to compare values in your queries.
Query
queryListMoviesByRating($minRating:Int!,$maxRating:Int!){movies(where:{rating:{ge:$minRating,lt:$maxRating}}){idtitle}}
Response
{"data":{"movies":[{"id":"some-uuid","title":"Example Movie Title"},{"id":"another-uuid","title":"Another Movie Title"}]}}
Filter withincludes andexcludes operators for array fields
You can test that an array field includes a specified item.
The following example illustrates theincludes operator.
Data Connect supportsincludesAll,excludes,excludesAll andmore. Review all such operators for integers, strings, dates and other datatypes in the_ListFilter headings of the reference documentation.
Query
queryListMoviesByTag($tag:String!){movies(where:{tags:{includes:$tag}}){# graphql: list the fields from the results to returnidtitle}}
Response
{"data":{"movies":[{"id":"some-uuid","title":"Example Movie Title"}]}}
Filter with string operations and regular expressions
Your queries can use typical string search and comparison operations, includingregular expressions. Note for efficiency you are bundling several operationshere and disambiguating them with aliases.
queryMoviesTitleSearch($prefix:String,$suffix:String,$contained:String,$regex:String){prefixed:movies(where:{title:{startsWith:$prefix}}){...}suffixed:movies(where:{title:{endsWith:$suffix}}){...}contained:movies(where:{title:{contains:$contained}}){...}}Filter with_or,_and,_not operator logic
Use_or for more complex logic.Data Connect also supports_andand_not operators.
Query
queryListMoviesByGenreAndGenre($minRating:Int!,$genre:String){movies(where:{_or:[{rating:{ge:$minRating}},{genre:{eq:$genre}}]}){# graphql: list the fields from the results to returntitle}}
Response
{"data":{"movies":[{"title":"Movie Title 1"},{"title":"Movie Title 2"}]}}
Write relational queries
Data Connect queries can access data based on the relationships amongtables. You can use the object (one-to-one) or array (one-to-many) relationshipsdefined in your schema to make nested queries, that is, fetch data for one typealong with data from a nested or related type.
Such queries use magicData Connect_on_ and_via syntax ingenerated read fields.
Remember to review thesample schema.
Many to one
Now look at a query to illustrate_on_ syntax.
Query
queryMyReviews@auth(level:USER){user(key:{id_expr:"auth.uid"}){reviews:reviews_on_user{movie{name}rating}}}
Response
{"data":{"user":{"reviews":[{"movie":{"name":"Movie Title"},"rating":5}]}}}
One to one
You can write a one-to-one query using_on_ syntax.
Query
queryGetMovieMetadata($id:UUID!)@auth(level:PUBLIC){movie(id:$id){movieMetadatas_on_movie{director}}}
Response
{"data":{"movie":{"movieMetadatas_on_movie":{"director":"Some Director"}}}}
Many to many
Many-to-many queries use_via_ syntax. A many-to-many query mightretrieve actors for a specified movie.
Query
queryMoviesActors($id:UUID!)@auth(level:USER){movie(id:$id){actors:actors_via_MovieActors{name}}}
Response
{"data":{"movie":{"actors":[{"name":"Actor Name"}]}}}
But we can write a more complex query, using aliases, to filter based onroleto retrieve actors and associated movies inmainActors andsupportingActors results. Since this is many-to-many,_via_ syntax is used.
Query
queryGetMovieCast($movieId:UUID!,$actorId:UUID!)@auth(level:PUBLIC){movie(id:$movieId){mainActors:actors_via_MovieActor(where:{role:{eq:"main"}}){name}supportingActors:actors_via_MovieActor(where:{role:{eq:"supporting"}}){name}}actor(id:$actorId){mainRoles:movies_via_MovieActor(where:{role:{eq:"main"}}){title}supportingRoles:movies_via_MovieActor(where:{role:{eq:"supporting"}}){title}}}
Response
{"data":{"movie":{"mainActors":[{"name":"Main Actor Name"}],"supportingActors":[{"name":"Supporting Actor Name"}]},"actor":{"mainRoles":[{"title":"Main Role Movie Title"}],"supportingRoles":[{"title":"Supporting Role Movie Title"}]}}}
Aggregation queries
What are aggregates, and why use them?
Aggregate fields let you perform calculations on a list of results. Withaggregate fields, you can do things like:
- Find the average score of a review
- Find the total cost of items in a shopping cart
- Find the highest- or lowest-rated product
- Count the number of products in your store
Aggregates are performed on the server, which offers a number of benefits overcalculating them client side:
- Faster app performance (since you avoid client side calculations)
- Reduced data egress costs (since you send just the aggregated results insteadof all of the inputs)
- Improved security (since you can give clients access to aggregated datainstead of the entire data set)
Example schema for aggregates
In this section, we'll switch to a storefront example schema, which is a goodfor explaining how to use aggregates:
typeProduct@table{name:String!manufacturer:String!quantityInStock:Int!price:Float!expirationDate:Date}Simple aggregates
_count for all fields
The simplest aggregate field is_count: it returns how many rows match yourquery. For each field in your type,Data Connectgenerates corresponding aggregate fields depending on the field type.
Query
queryCountProducts{products{_count}} Responseone
one
For example, if you have 5 products in your database, the result would be:
{"products":[{"_count":5}]}All fields have a<field>_count field, which counts how many rows have anon-null value in that field.
Query
queryCountProductsWithExpirationDate{products{expirationDate_count}} Responsefield_count
field_count
For example, if you have 3 products with an expiration date, the result wouldbe:
{"products":[{"expirationDate_count":3}]}_min, _max, _sum, and _avg for numeric fields
Numeric fields (int, float, int64) also have<field>_min,<field>_max,<field>_sum, and<field>_avg.
Query
queryNumericAggregates{products{quantityInStock_maxprice_minprice_avgquantityInStock_sum}} Response_min _max _sum _avg
_min _max _sum _avg
For example, if you have the following products:
- Product A:
quantityInStock: 10,price: 2.99 - Product B:
quantityInStock: 5,price: 5.99 - Product C:
quantityInStock: 20,price: 1.99
The result would be:
{"products":[{"quantityInStock_max":20,"price_min":1.99,"price_avg":3.6566666666666666,"quantityInStock_sum":35}]}_min and _max for dates and timestamps
Date and timestamp fields have<field>_min and<field>_max.
Query
queryDateAndTimeAggregates{products{expirationDate_maxexpirationDate_min}} Response_min _maxdatetime
_min _maxdatetime
For example, if you have the following expiration dates:
- Product A:
2024-01-01 - Product B:
2024-03-01 - Product C:
2024-02-01
The result would be:
{"products":[{"expirationDate_max":"2024-03-01","expirationDate_min":"2024-01-01"}]}Distinct
Thedistinct argument lets you get all unique values for a field(or combination of fields). For example:
Query
queryListDistinctManufacturers{products(distinct:true){manufacturer}} Responsedistinct
distinct
For example, if you have the following manufacturers:
- Product A:
manufacturer: "Acme" - Product B:
manufacturer: "Beta" - Product C:
manufacturer: "Acme"
The result would be:
{"products":[{"manufacturer":"Acme"},{"manufacturer":"Beta"}]}You can also use thedistinct argument on aggregate fields to insteadaggregate the distinct values. For example:
Query
queryCountDistinctManufacturers{products{manufacturer_count(distinct:true)}} Responsedistinctonaggregate
distinctonaggregate
For example, if you have the following manufacturers:
- Product A:
manufacturer: "Acme" - Product B:
manufacturer: "Beta" - Product C:
manufacturer: "Acme"
The result would be:
{"products":[{"manufacturer_count":2}]}Grouped aggregates
You perform a grouped aggregate by selecting a mix of aggregate andnon-aggregate fields on a type. This groups together all matching rows that havethe same value for the non-aggregate fields, and calculate the aggregate fieldsfor that group. For example:
Query
queryMostExpensiveProductByManufacturer{products{manufacturerprice_max}} Responsegroupedaggregates
groupedaggregates
For example, if you have the following products:
- Product A:
manufacturer: "Acme",price: 2.99 - Product B:
manufacturer: "Beta",price: 5.99 - Product C:
manufacturer: "Acme",price: 1.99
The result would be:
{"products":[{"manufacturer":"Acme","price_max":2.99},{"manufacturer":"Beta","price_max":5.99}]}having andwhere with grouped aggregates
You can also use thehaving andwhere argument to only return groups thatmeet a provided criteria.
havinglets you filter groups by their aggregate fieldswherelets you filter the rows based on non-aggregate fields.
Query
queryFilteredMostExpensiveProductByManufacturer{products(having:{price_max:{ge:2.99}}){manufacturerprice_max}} Responsehavingwhere
havingwhere
For example, if you have the following products:
- Product A:
manufacturer: "Acme",price: 2.99 - Product B:
manufacturer: "Beta",price: 5.99 - Product C:
manufacturer: "Acme",price: 1.99
The result would be:
{"products":[{"manufacturer":"Acme","price_max":2.99},{"manufacturer":"Beta","price_max":5.99}]}Aggregates across tables
Aggregate fields can be used in concert with generated one-to-many relationshipfields to answer complex questions about your data. Here is amodified schema, with separate table,Manufacturer, we can use in examples:
typeProduct@table{name:String!manufacturer:Manufacturer!quantityInStock:Int!price:Float!expirationDate:Date}typeManufacturer@table{name:String!headquartersCountry:String!}Now we can use aggregate fields to do things like find how many products amanufacturer makes:
Query
queryGetProductCount($id:UUID){manufacturers{nameproducts_on_manufacturer{_count}}} Responseaggregatesacrosstables
aggregatesacrosstables
For example, if you have the following manufacturers:
- Manufacturer A:
name: "Acme",products_on_manufacturer: 2 - Manufacturer B:
name: "Beta",products_on_manufacturer: 1
The result would be:
{"manufacturers":[{"name":"Acme","products_on_manufacturer":{"_count":2}},{"name":"Beta","products_on_manufacturer":{"_count":1}}]}Query enumeration fields
You can queryenumeration fields in your schema.
Assume our enumeration is implemented as:
enumAspectRatio{ACADEMYWIDESCREENANAMORPHICIMAX"Noinformationavailableonaspectratio"UNAVAILABLE}For example, you can list all movies formatted in your preferred aspect ratio.
queryListMoviesByAspectRatio($aspectRatio:AspectRatio!)@auth(level:PUBLIC){movies(where:{originalAspectRatio:{eq:$aspectRatio}}){idtitleimageUrlreleaseYeargenreratingtagsdescriptionotherAspectRatios}}Filter enumeration fields
Enums have the standard filtering actions available:eq,ne,ge,gt,lt,le,innin, as well asincludes,excludes,includesAll andexcludesAll for lists.
Inorder, the greater and less than operators use the order of the enumvalues for meaningful execution. Values at the beginning of the enumare smaller than values at the end of the enum definition.
For example, the followingwhere clause would return all movies withratio less than the input aspect ratio.
- In the first example, if
$aspectRatiowere IMAX, then movies withratiomatching ACADEMY, WIDESCREEN, and ANAMORPHIC would be selected. - In the second example, only movies available in both ACADEMY and WIDESCREENwould be returned.
movies(where:{originalAspectRatio:{lt:$aspectRatio}})movies(where:{otherAspectRatios:{includesAll:[ACADEMY,WIDESCREEN]}})Perform aggregation queries on enumeration fields
Only_count is supported for enums.
The following query counts the total number of movies of each aspect ratio.
queryMovieCountByOriginalAspectRatio@auth(level:PUBLIC){movies{_countoriginalAspectRatio}}Call enumeration queries from client code
Learn how to use enumeration queries from clients, and how to design clientcalls to maximize your flexibility in managing enumerations, in the SDK guidesforiOS,Android,webandFlutter.
Write advanced queries: usequery fields to read data in multi-step operations
There are many situations in which you might want to read your databaseduring execution of a mutation to lookup and verify existing data beforeperforming, for example, inserts or updates. These options save round tripoperations and hence costs.
Data Connect supports this functionality.Seemulti-step operations.
Next steps
Note: Follow the complete series on building Data Connect schemas and connectors, which covers:
- Schema development
- Query development (this guide)
- Mutation development
You may be interested in:
- Generating queries for your apps usingAI assistance tools
- Authorizing your queries per theauthorization guide
- Calling queries from your client code forweb,iOS,AndroidandFlutter.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-10 UTC.