Relay Global Object Identification Specification

Relay’s support for object identification relies on the GraphQL server exposing object identifiers in a standardized way. In the query, the schema should provide a standard mechanism for asking for an object by ID. In the response, the schema provides a standard way of providing these IDs.

We refer to objects with identifiers as “nodes”.

An example of both of those is the following query:

{
  node(id: "4") {
    id
    ... on User {
      name
    }
  }

This section of the spec describes the formal requirements around object refetching.

Contents
  1. 1Reserved Types
  2. 2Node Interface
    1. 2.1Introspection
  3. 3Node root field
    1. 3.1Introspection
  4. 4Field stability
  5. 5Plural identifying root fields
    1. 5.1Fields

1Reserved Types

A GraphQL Relay server must reserve certain types and type names to support the object identification model used by Relay. In particular, this spec creates guidelines for the following types:

2Node Interface

The server must provide an interface called Node. That interface must include exactly one field, called id that returns a non‐null ID.

This id should be a globally unique identifier for this object, and given just this id, the server should be able to refetch the object.

2.1Introspection

A server that correctly implements the above interface will accept the following introspection query, and return the provided response:

{
  __type(name: "Node") {
    name
    kind
    fields {
      name
      type {
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

yields

{
  "__type": {
    "name": "Node",
    "kind": "INTERFACE",
    "fields": [
      {
        "name": "id",
        "type": {
          "kind": "NON_NULL",
          "ofType": {
            "name": "ID",
            "kind": "SCALAR"
          }
        }
      }
    ]
  }
}

3Node root field

The server must provide a root field called node that returns the Node interface. This root field must take exactly one argument, a non‐null ID named id.

If a query returns an object that implements Node, then this root field should refetch the identical object when value returned by the server in the Node‘s id field is passed as the id parameter to the node root field.

The server must make a best effort to fetch this data, but it may not always be possible; for example, the server may return a User with a valid id, but when the request is made to refetch that user with the node root field, the user’s database may be unavailable, or the user may have deleted their account. In this case, the result of querying this field should be null.

3.1Introspection

A server that correctly implements the above requirement will accept the following introspection query, and return a response that contains the provided response.

{
  __schema {
    queryType {
      fields {
        name
        type {
          name
          kind
        }
        args {
          name
          type {
            kind
            ofType {
              name
              kind
            }
          }
        }
      }
    }
  }
}

yields

{
  "__schema": {
    "queryType": {
      "fields": [
        // This array may have other entries
        {
          "name": "node",
          "type": {
            "name": "Node",
            "kind": "INTERFACE"
          },
          "args": [
            {
              "name": "id",
              "type": {
                "kind": "NON_NULL",
                "ofType": {
                  "name": "ID",
                  "kind": "SCALAR"
                }
              }
            }
          ]
        }
      ]
    }
  }
}

4Field stability

If two objects appear in a query, both implementing Node with identical IDs, then the two objects must be equal.

For the purposes of this definition, object equality is defined as follows:

For example:

{
  fourNode: node(id: "4") {
    id
    ... on User {
      name
      userWithIdOneGreater {
        id
        name
      }
    }
  }
  fiveNode: node(id: "5") {
    id
    ... on User {
      name
      userWithIdOneLess {
        id
        name
      }
    }
  }
}

might return:

{
  "fourNode": {
    "id": "4",
    "name": "Mark Zuckerberg",
    "userWithIdOneGreater": {
      "id": "5",
      "name": "Chris Hughes"
    }
  },
  "fiveNode": {
    "id": "5",
    "name": "Chris Hughes",
    "userWithIdOneLess": {
      "id": "4",
      "name": "Mark Zuckerberg",
    }
  }
}

Because fourNode.id and fiveNode.userWithIdOneLess.id are the same, we are guaranteed by the conditions above that fourNode.name must be the same as fiveNode.userWithIdOneLess.name, and indeed it is.

5Plural identifying root fields

Imagine a root field named username, that takes a user’s username and returns the corresponding user:

{
  username(username: "zuck") {
    id
  }
}

might return:

{
  "username": {
    "id": "4",
  }
}

Clearly, we can link up the object in the response, the user with ID 4, with the request, identifying the object with username “zuck”. Now imagine a root field named usernames, that takes a list of usernames and returns a list of objects:

{
  usernames(usernames: ["zuck", "moskov"]) {
    id
  }
}

might return:

{
  "usernames": [
    {
      "id": "4",
    },
    {
      "id": "6"
    }
  ]
}

For Relay to be able to link the usernames to the responses, it needs to know that the array in the response will be the same size as the array passed as an argument, and that the order in the response will match the order in the argument. We call these plural identifying root fields, and their requirements are described below.

5.1Fields

A Relay‐compliant server may expose root fields that accept a list of input arguments, and returns a list of responses. For Relay to use these fields, these fields must be plural identifying root fields, and obey the following requirements.

Relay‐compliant servers may expose root fields that are not plural identifying root fields; the Relay client will just be unable to use those fields as root fields in its queries.

Plural identifying root fields must have a single argument. The type of that argument must be a non‐null list of non‐nulls. In our usernames example, the field would take a single argument named usernames, whose type (using our type system shorthand) would be [String!]!.

The return type of a plural identifying root field must be a list, or a non‐null wrapper around a list. The list must wrap the Node interface, an object that implements the Node interface, or a non‐null wrapper around those types.

Whenever the plural identifying root field is used, the length of the list in the response must be the same as the length of the list in the arguments. Each item in the response must correspond to its item in the input; more formally, if passing the root field an input list Lin resulted in output value Lout, then for an arbitrary permutation P, passing the root field P(Lin) must result in output value P(Lout).

Because of this, servers are advised to not have the response type wrap a non‐null wrapper, because if it is unable to fetch the object for a given entry in the input, it still must provide a value in the output for that input entry; null is a useful value for doing so.

  1. 1Reserved Types
  2. 2Node Interface
    1. 2.1Introspection
  3. 3Node root field
    1. 3.1Introspection
  4. 4Field stability
  5. 5Plural identifying root fields
    1. 5.1Fields