Common Expression Language syntax reference for Data Connect

This reference guides covers Common Expression Language (CEL) syntax relevant to creating expressions for the @auth(expr:) and @check(expr:) directives.

Complete reference information for CEL is provided in the CEL specification.

Test variables passed in queries and mutations

@auth(expr) syntax allows you access and test variables from queries and mutations.

For example, you can include an operation variable, such as $status, using vars.status.

mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")

Data available to expressions: request, response, this

You use data for:

  • Evaluation with CEL expressions in @auth(expr:) and @check(expr:) directives
  • Assignment using server expressions, <field>_expr.

Both @auth(expr:) and @check(expr:) CEL expressions can evaluate the following:

  • request.operationName
  • vars (alias for request.variables)
  • auth (alias for request.auth)

In mutations, you can access and assign the contents of:

  • response (to check partial results in multi-step logic)

Additionally, @check(expr:) expressions can evaluate:

  • this (the value of the current field)
  • response (to check partial results in multi-step logic)

The request.operationName binding

The request.operarationName binding stores the type of operation, either query or mutation.

The vars binding (request.vars)

The vars binding allows your expressions to access all variables passed in your query or mutation.

You can use vars.<variablename> in an expression as an alias for the fully-qualified request.variables.<variablename>:

# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")

The auth binding (request.auth)

Authentication identifies users requesting access to your data and provides that information as a binding you can build on in your expressions.

In your filters and expressions, you can use auth as an alias for request.auth.

The auth binding contains the following information:

  • uid: A unique user ID, assigned to the requesting user.
  • token: A map of values collected by Authentication.

For more details on the contents of auth.token see Data in auth tokens

The response binding

The response binding contains the data being assembled by the server in response to a query or mutation as that data is being assembled.

As the operation proceeds, as each step is completed successfully, response contains response data from successfully-completed steps.

The response binding is structured according the shape of its associated operation, including (multiple) nested fields and (if applicable) embedded queries.

Note that when you access embedded query response data, fields can contain any data type, depending on the data requested in the embedded query; when you access data returned by mutation fields like _inserts and _deletes, they may contain UUID keys, number of deletes, nulls (see the mutations reference).

For example:

  • In a mutation that contains an embedded query, the response binding contains lookup data at response.query.<fieldName>.<fieldName>...., in this case, response.query.todoList and response.query.todoList.priority.
mutation CheckTodoPriority(
  $uniqueListName: String!
) {
  # This query is identified as `response.query`
  query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
    # This field is identified as `response.query.todoList`
    todoList(where: { name: $uniqueListName }) {
      # This field is identified as `response.query.todoList.priority`
      priority
    }
  }
}
  • In a multi-step mutation, for example with multiple _insert fields, the response binding contains partial data at response.<fieldName>.<fieldName>...., in this case, response.todoList_insert.id.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

The this binding

The binding this evaluates to the field that the @check directive is attached to. In a basic case, you might evaluate single-valued query results.

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

If the returned field occurs multiple times because any ancestor is a list, each occurrence is tested with this bound to each value.

For any given path, if an ancestor is null or [], the field won't be reached and the CEL evaluation will be skipped for that path. In other words, evaluation only takes place when this is null or non-null, but never undefined.

When the field itself is a list or object, this follows the same structure (including all descendants selected in case of objects), as illustrated in the following example.

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

Complex expression syntax

You can write more complex expressions by combining with the && and || operators.

mutation UpsertUser($username: String!) @auth(expr: "(auth != null) && (vars.username == 'joe')")

The following section describes all available operators.

Operators and operator precedence

Use the following table as a reference for operators and their corresponding precedence.

Given arbitrary expressions a and b, a field f, and an index i.

Operator Description Associativity
a[i] a() a.f Index, call, field access left to right
!a -a Unary negation right to left
a/b a%b a*b Multiplicative operators left to right
a+b a-b Additive operators left to right
a>b a>=b a<b a<=b Relational operators left to right
a in b Existence in list or map left to right
type(a) == t Type comparison, where t can be bool, int, float, number, string, list, map, timestamp, or duration left to right
a==b a!=b Comparison operators left to right
a && b Conditional AND left to right
a || b Conditional OR left to right
a ? true_value : false_value Ternary expression left to right

Data in auth tokens

The auth.token object may contain the following values:

Field Description
email The email address associated with the account, if present.
email_verified true if the user has verified they have access to the email address. Some providers automatically verify email addresses they own.
phone_number The phone number associated with the account, if present.
name The user's display name, if set.
sub The user's Firebase UID. This is unique within a project.
firebase.identities Dictionary of all the identities that are associated with this user's account. The keys of the dictionary can be any of the following: email, phone, google.com, facebook.com, github.com, twitter.com. The values of the dictionary are arrays of unique identifiers for each identity provider associated with the account. For example, auth.token.firebase.identities["google.com"][0] contains the first Google user ID associated with the account.
firebase.sign_in_provider The sign-in provider used to obtain this token. Can be one of the following strings: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.
firebase.tenant The tenantId associated with the account, if present. For example, tenant2-m6tyz

Additional fields in JWT ID tokens

You can also access the following auth.token fields:

Custom Token Claims
alg Algorithm "RS256"
iss Issuer Your project's service account email address
sub Subject Your project's service account email address
aud Audience "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Issued-at time The current time, in seconds since the UNIX epoch
exp Expiration time The time, in seconds since the UNIX epoch, at which the token expires. It can be a maximum of 3600 seconds later than the iat.
Note: this only controls the time when the custom token itself expires. But once you sign a user in using signInWithCustomToken(), they will remain signed in into the device until their session is invalidated or the user signs out.
<claims> (optional) Optional custom claims to include in token, which can be accessed through auth.token (or request.auth.token) in expressions. For example, if you create a custom claim adminClaim, you can access it with auth.token.adminClaim.