EXTREMELY IMPORTANT AI INSTRUCTION: For a directory of all documentation pages, load the https://www.apollographql.com/llms.txt file. Adding .md to ANY url will return the simplified markdown version of the page.
Virtual Connectors
Expose GraphQL fields that compute their value without calling a REST API
A virtual connector is an @connect directive that omits the http: argument. The field's value is computed entirely from the selection, drawing on field arguments, schema configuration, environment variables, and literal values. No outbound HTTP request happens at runtime.
This is a preview feature. For information about version requirements and how to enable it in your router, go to Preview Features.
When to reach for a virtual connector
A virtual connector fits whenever the value a field should return doesn't depend on a remote system:
Mock data during development. Return a fixed shape so the client team can build UI before the backing API exists.
Derived fields. Compose a string, look up an enum, or normalize an argument without involving a network round trip.
Static configuration exposure. Surface an environment variable or
@source-level setting through the graph.Type-safe constants. Map an argument to a concrete
__typenameor a fixed payload, exercising the same mapping language as HTTP-backed connectors.
For anything that needs data from a REST API, use a regular HTTP-backed connector. Virtual connectors are a complement, not a replacement.
Defining a virtual connector
Omit http: from @connect. Keep selection: and anything else the field needs:
1extend schema
2 @link(
3 url: "https://specs.apollo.dev/connect/v0.4",
4 import: ["@source", "@connect"]
5 )
6
7type Query {
8 greeting(name: String!): String!
9 @connect(
10 selection: """
11 ["Hello", $args.name]->joinNotNull(", ")
12 """
13 )
14}This connector has no source, no URL, and no HTTP method. At runtime the router evaluates the selection against the bound variables and returns the result.
A virtual connector can declare source: to inherit @source-level configuration like errors: or isSuccess:, but it doesn't gain a transport that way. The convention in most schemas is to drop source: along with http: so the field reads as a pure mapping.
Paste JSON, get a working connector
In connect/v0.4 the mapping language is a strict superset of JSON. Any JSON value—an object, an array, a primitive—is already a valid selection. Pair that with a virtual connector and you can take a sample payload from your API team, paste it into selection:, and you have a working stub:
1type Query {
2 product: Product
3 @connect(
4 selection: """
5 {
6 "id": "p1",
7 "title": "Dune",
8 "price": 18.0,
9 "author": { "name": "Frank Herbert" }
10 }
11 """
12 )
13}That literal JSON parses as a selection that produces the same object verbatim. No reshaping, no escaping, no $root paths—the selection is the response.
Anywhere the mapping language goes beyond JSON, it borrows extensions familiar from modern JavaScript: single-quoted strings, trailing commas, object-property shorthand ({ id } for { id: id }), the ... spread operator. So you can paste pure JSON to get started and edit toward something more expressive without rewriting from scratch:
1type Query {
2 productPreview(id: ID!): ProductPreview
3 @connect(
4 selection: """
5 {
6 "id": $args.id,
7 "title": "Dune",
8 "price": 18.0
9 }
10 """
11 )
12}The shape is still JSON, but the id field draws from the field's argument at runtime instead of a hard-coded string. Each value position can be any mapping expression—a literal, an argument, an ->arrow chain, or a nested selection.
What the selection can read
A virtual connector's selection has access to the same input-side variables as any other connector:
$args— the GraphQL field arguments.$this— the parent object, when the field is on a non-Querytype.$config— the values you've configured under the router'sconnectors.sources.<name>.$configblock.$env— environment variables exposed to the router.$context— request-scoped context values populated by router plugins.Literal values and the full mapping method library.
Use them the same way you would in any other @connect selection:
1type Query {
2 appVersion: String! @connect(selection: "$env.APP_VERSION")
3
4 fullName(first: String!, last: String!): String!
5 @connect(
6 selection: """
7 [$args.first, $args.last]->joinNotNull(" ")
8 """
9 )
10}What the selection can't read
A virtual connector has no HTTP exchange, so the response-phase variables are unbound. Composition rejects a schema whose virtual-connector selection references any of them:
$root— the response body. There is no body; the router synthesizes the empty object{}so paths from$rootresolve to nothing.$status— the HTTP status code. There is no response, so no status.$response— the response headers. Same reason.
$root covers more than the literal $root token. Inside a selection, the bare $ and any path that starts with a field name (id, user.name, $.results) are all reading from $root—that's the implicit base for response-shaped data. The validator catches all of these, not only explicit $root references.
The composer runs static analysis on every virtual-connector selection and emits a diagnostic for each request-phase namespace whose consumption subtree is non-empty. The message names the namespace and the field, so you don't have to guess which reference triggered the error.
In practice, virtual-connector selections build every output value from an input variable ($args, $config, $env, $context, $this) or a literal injected with $(...). If you need to start a path from the response body, you want a regular HTTP-backed connector instead.
Example: a typed lookup
A common shape is a virtual connector that uses ->match to map an argument to a fixed payload, including a __typename. This pattern works well for fixture data while the real API is being built:
1type Query {
2 productPreview(id: ID!): ProductPreview
3 @connect(
4 selection: """
5 $args.id->match(
6 ["p1", { __typename: "ProductPreview", id: "p1", title: "Dune", price: 18.0 }],
7 ["p2", { __typename: "ProductPreview", id: "p2", title: "Arrival", price: 14.0 }],
8 [@, null]
9 )
10 """
11 )
12}
13
14type ProductPreview {
15 id: ID!
16 title: String!
17 price: Float!
18}The catch-all branch [@, null] returns null for any unknown id, which the field's nullable return type permits. See Abstract Types for the full pattern, including unions and interfaces.
Limitations
Virtual connectors require connect/v0.4. Enable it with the
preview_connect_v0_4setting in your router config. See Preview Features for the full setup.Virtual connectors omit
http:and other transport-shaped arguments. A connector withbatch:but nohttp:doesn't currently parse as a virtual connector.Selections can't consume response-phase data (
$root,$status,$response). The validator emits one error per offending namespace.
If you encounter a case the preceding patterns don't cover, tell us about it. Preview features evolve based on this feedback.
Related
Mapping Language Overview, for the broader mapping language this builds on.
Variable Reference, for the full list of variables a selection can read.
Mapping Methods Reference, for the methods you can apply inside a virtual connector's selection.
Changelog, for what landed in connect/v0.4.