Mapping Language
Convert data to and from GraphQL types using the Apollo Connectors mapping language
This reference describes the Apollo Connectors mapping language. You use the mapping language anywhere data needs to be transformed from one form to another. This includes building URIs and headers, constructing request bodies when making requests, and mapping response data to GraphQL schema.
The basics
The mapping language uses a declarative approach to transforming data from one shape to another. Unlike traditional programming languages, it doesn't have state or loops. Since the mapping language borrows heavily from the GraphQL query language, some concepts like the alias syntax and array handling might look familiar.
Fields and paths
The most basic mapping expression selects a value from a path.
For example, a URI path might include the expression $args.id
.
This expression means, "Use the id
field of the $args
variable."
For example:
type Query {
post(id: ID!): Post
@connect(
source: "jsonPlaceholder"
http: { GET: "/posts/{$args.id}" }
selection: "id title"
)
}
When applied to an array, the result of a path is also an array.
For example, if $args.filters
is an array of objects, $args.filters.value
is an array of the value
property of each object.
{
"filters": [
{ "value": 1 },
{ "value": 2 },
{ "value": 3 }
]
}
[1, 2, 3]
Creating objects
For this section, assume that $args
contains this JSON object:
{ "handle": "alice", "email": "alice@example.com" }
When you give a value a name, you create an object containing that value:
username: $args.handle
{ "username": "alice" }
You can combine multiple fields to create an object with multiple properties:
username: $args.handle
email: $args.email
{
"username": "alice",
"email": "alice@example.com"
}
Nested objects
You can create a new object as the property of another object using curly braces ({}
):
user: {
username: $args.handle
email: $args.email
}
{
"user": {
"username": "alice",
"email": "alice@example.com"
}
}
Subselections
If multiple properties from an object come from another object, you can use a subselection to avoid repeating the object name. The $
variable in the curly braces refers to the selected object—whatever comes directly before the {
. In the following example, that's $args
.
$args {
username: $.handle
email: $.email
}
{
"username": "alice",
"email": "alice@example.com"
}
As shorthand, you can omit $.
and refer to the fields only by their names:
$args {
username: $.handle
email: $.email
}
$args {
username: handle
email: email
}
When the name of the new field matches the name of the source, you can also omit the left-hand side:
$args {
username: handle
email: email
}
$args {
username: handle
email
}
You can combine this with the ability to create nested objects by adding back in the user:
field name:
user: $args {
username: handle
email
}
{
"user": {
"username": "alice",
"email": "alice@example.com"
}
}
Multiple levels
For more complex inputs, you can use multiple subselections to achieve the desired result without repetition. Suppose $args
looks like this:
{
"user": {
"handle": "alice",
"email": "alice@example.com"
},
"company": {
"name": "Acme",
"address": "123 Main St"
}
}
You can select all the data like this:
$args {
user {
username: handle
email
}
organization: company {
name
address
}
}
{
"user": {
"username": "alice",
"email": "alice@example.com"
},
"organization": {
"name": "Acme",
"address": "123 Main St"
}
}
user
property being creating comes from the parent's property of the same name, you can use the shorthand
syntax when creating the object.Special case for selection
For the @connect
directive's selection
argument only, a top-level $
refers to the root of the response body from the source API.
This means you can select basic GraphQL fields succinctly.
For example, given this JSON response:
{
"id": "1",
"username": "alice",
"email": "alice@example.com"
}
You can use this basic selection
to select all of a user's fields.
type Query {
user(id: ID!): User
@connect(
http: { GET: "https://api.example.com/users/{$args.id}" }
selection: "id username email"
)
}
type User {
id: ID!
username: String!
email: String!
}
Since the REST endpoint returns username
and email
in its
response, you can map them directly to fields of the same name in the GraphQL schema using the shorthand that omits the $
.
Variables
Variables are the sources of data to be transformed. Variables can refer to:
GraphQL arguments
sibling fields
request and response data
information from the router
The available variables depend on the context of the expression.
Variable | Definition | Availability Notes |
---|---|---|
| The arguments passed to the field in the GraphQL query.
For a field defined like product(id: ID!): Product , $args.id refers to the id argument passed to the product field. |
|
| The parent object of the current field. Can be used to access sibling fields. Learn about dependencies $this can create. |
|
| The GraphOS Router configuration. | Always available. |
| Context set by router customizations like coprocessors. | Only available if router customizations exist where context has been set. |
| The HTTP status code of a response. |
|
| At the top level, $ refers to the root of the API response body.Within a {...} sub-selection, $ refers to the value of the parent. See an example. |
|
| The value being transformed with a method. Behaves differently depending on the context. Learn more. | Depends on the specific transformation method or mapping being applied. |
Additional variable notes
$this
Using $this
creates a dependency between the connector and fields on the parent object. This implies that another connector or another subgraph can provide this field.
type Product {
id: ID!
reviews: [Review]
# Some other connector or subgraph provides the `id` value.
@connect(
http: { GET: "/products/{$this.id}/reviews" }
selection: "id rating comment"
)
}
This feature of $this
is equivalent to the @requires
directive in Apollo Federation.
For example, the following connectors usage is equivalent to the following @requires
usage in a subgraph schema.
type Product {
id: ID!
weight: Int
shippingCost: Int
@connect(
http: {GET: "/shipping?weight=${this.weight}" }
selection: "$"
)
}
type Product @key(fields: "id") {
id: ID!
weight: Int @external
shippingCost: Int @requires(fields: "weight")
}
In fact, you can combine connectors with @requires
to create computed fields using REST APIs.
$
When used at the top-level of an expression, $
is only valid within the selection
field of a connector, and refers
to the root of the response body from the API. When used within a {...}
sub-selection, $
refers to the value of the parent.
$.results { # Here `$` refers to the response body
id: $.id # `$` refers to the `results` field
}
@
Refers to the value being transformed with a method, which is sometimes the same as $
, but not always.
For example, in the value->echo({ wrapped: @ })
example in the methods section, @
refers to the value of value
, while $
refers to the object containing value
.
Additionally, in the colors->map({ name: @ })
example, the method binds @
to each element in the $.colors
list, while $
remains unchanged.
Literal values
You can use literal values with $()
. This is useful for adding constant values to the request body.
body: """
hello: $("world")
theAnswer: $(42)
isTrue: $(true)
anObject: $({ key: "value" })
aList: $([1, 2, 3])
"""
To avoid using the $()
wrapper repeatedly, you can wrap an entire object with $()
:
body: """
$({
hello: "world",
theAnswer: 42,
isTrue: true,
anObject: { key: "value" },
aList: [1, 2, 3],
})
"""
Inside the $()
expression, you can use any JSON literal: numbers, strings, booleans, arrays, objects, and null
.
Variables in literals
You can use variables, like $args
and $this
, in literal values.
1selection: """
2names: $([ # a list field like `names: [String]`
3 $args.input.name, # a variable
4 results->first.name # a selection
5])
6"""
Given the following inputs for the example above:
{
"$args": {
"input": {
"name": "Alice"
}
}
}
{
"results": [
{ "name": "Bob" }
{ "name": "Charlie" }
]
}
The resulting selection
is:
{ "names": ["Alice", "Bob"] }
Using literals in with @connect
Currently, the following rules apply when using literal values with the @connect
directive:
All literal values in the
body
argument are always allowed.Scalar literal values (strings, numbers, booleans) and lists of scalars are allowed in the
selection
argument.Literal values can't be mapped to a field that returns a nested object or list of nested objects.
Literal objects and lists and lists of objects are allowed only if they're mapped to a custom scalar field.
See corresponding examples of these rules below. The number in parentheses, for example, (1)
on line 6 shows an example of rule 1.
1type Mutation {
2 createPost(input: CreatePostInput!): PostPayload
3 @connect(
4 http: {
5 POST: "https://api.example.com/posts"
6 body: "$({ id: 1, title: "Hello, world!" })" # ✅ (1)
7 }
8 selection: """
9 success: $(true) # ✅ (2)
10 post: $({ id: 1, title: "Hello, world!" }) # ❌ (3)
11 metadata: $({ key: "value" }) # ✅ (4)
12 """
13 )
14
15type PostPayload {
16 success: Boolean
17 post: Post # ⚠️ Object type
18 metadata: JSON
19}
20
21scalar JSON
Methods
You can transform values using the ->method
syntax.
The arguments passed to a method can be either JSON literals (numbers, strings, booleans, arrays, objects, and null
) or the following variables: $
, $args
, $this
, or $config
.
The mapping language provides the following methods:
Method | Description | Example |
---|---|---|
echo | Evaluates and returns its first argument | wrappedValue: value->echo({ wrapped: @ }) |
map | Maps a list of values to a new list of values | colors: colors->map({ name: @ }) |
match | Replaces a value with a new value if it match another value | status: status->match([1, "one"], [2, "two"], [@, "other"]) |
first | Returns the first value in a list | firstColor: colors->first |
last | Returns the last value in a list | lastColor: colors->last |
slice | Returns a slice of a list | firstTwoColors: colors->slice(0, 2) |
size | Returns the length of a list | colorCount: colors->size |
entries | Returns a list of key-value pairs | keyValuePairs: colors->entries |
jsonStringify | Converts a value to a JSON string | jsonBody: body->jsonStringify |
Value transformation recipes
Enum value mapping
status: status->match(
["active", "ACTIVE"],
["not active", "INACTIVE"],
[@, "UNKNOWN"] # fallback — the value always matches `@`
)
null
if nullable or result in a validation error if non-nullable.Convert a map into a list of key value pairs
{
"colors": {
"red": "#ff0000",
"green": "#00ff00",
"blue": "#0000ff"
}
}
colors: colors->entries
{
"colors": [
{ "key": "red", "value": "#ff0000" },
{ "key": "green", "value": "#00ff00" },
{ "key": "blue", "value": "#0000ff" }
]
}
colors: colors->entries {
name: key
hex: value
}
{
"colors": [
{ "name": "red", "hex": "#ff0000" },
{ "name": "green", "hex": "#00ff00" },
{ "name": "blue", "hex": "#0000ff" }
]
}
List management
results->first
$([$.result])