---
title: GraphQL Harness
sidebarTitle: GraphQL Harness
---
import { Callout } from 'nextra/components';
# GraphQL Harness
<Callout type="info">
`GraphQLHarness` is new in GraphQL.js v17. It customizes the phases used by
`graphql()` and `graphqlSync()`.
</Callout>
`graphql()` is the convenience entry point that parses, validates, and executes
a GraphQL operation. In v17, those phases are represented by a harness:
```ts
type GraphQLHarness = {
parse: GraphQLParseFn;
validate: GraphQLValidateFn;
execute: GraphQLExecuteFn;
subscribe: GraphQLSubscribeFn;
};
```
`defaultHarness` is the built-in harness used by `graphql()` and
`graphqlSync()`.
```js
import { defaultHarness, graphql } from 'graphql';
const result = await graphql({
schema,
source,
harness: defaultHarness,
});
```
## Why this exists
The harness is a host integration API modeled after
[Envelop](https://the-guild.dev/graphql/envelop), The Guild's GraphQL plugin
system. Envelop showed that many servers need to customize the same request
phases: parsing, validation, execution, subscription execution, and the
cross-cutting behavior around those phases.
GraphQL.js remains a reference implementation, not a full plugin framework.
The harness brings the broader phase types used by that ecosystem closer to the
reference implementation so frameworks and plugin systems can share a common
shape. For example, `GraphQLParseFn` can return a `DocumentNode` or a promise
for a `DocumentNode`, even though the built-in GraphQL.js `parse()` function is
synchronous.
For application servers, prefer Envelop or a framework built on Envelop over
using a raw `GraphQLHarness` directly. The goal is that frameworks can accept a
custom harness, and plugin systems that customize these phases can interoperate
without each framework inventing a different integration surface.
## What can be customized
Each harness function receives the same arguments as the corresponding
GraphQL.js phase. The difference is that a harness phase may finish immediately
or by returning a promise:
```ts
type MaybePromise<T> = T | Promise<T>;
type GraphQLParseFn = (
source: string | Source,
options?: ParseOptions,
) => MaybePromise<DocumentNode>;
type GraphQLValidateFn = (
schema: GraphQLSchema,
documentAST: DocumentNode,
rules?: readonly ValidationRule[],
options?: ValidationOptions,
) => MaybePromise<readonly GraphQLError[]>;
type GraphQLExecuteFn = (args: ExecutionArgs) => MaybePromise<ExecutionResult>;
type GraphQLSubscribeFn = (
args: ExecutionArgs,
) => MaybePromise<
ExecutionResult | AsyncGenerator<ExecutionResult, void, void>
>;
```
Any harness phase may return synchronously or asynchronously. `graphqlSync()`
still requires every phase and resolver it reaches to complete synchronously.
`GraphQLExecuteFn` deliberately returns only `ExecutionResult`; it does not
include the experimental incremental delivery result type.
## Cached documents
A host that has a trusted document cache can replace the parse phase while
keeping the default validation and execution behavior.
```js
import { defaultHarness, graphql } from 'graphql';
const harness = {
...defaultHarness,
parse(source, options) {
const cached = documents.get(String(source));
return cached ?? defaultHarness.parse(source, options);
},
};
const result = await graphql({
schema,
source,
variableValues,
operationName,
harness,
});
```
## External validation
A host can also replace validation. This is useful for persisted operation
registries that validate at build time and return stored validation results at
runtime.
```js
import { defaultHarness, graphql } from 'graphql';
const harness = {
...defaultHarness,
async validate(schema, document, rules, options) {
const cached = await registry.getValidationResult(document, schema);
return cached ?? defaultHarness.validate(schema, document, rules, options);
},
};
const result = await graphql({
schema,
source,
harness,
});
```
## Relationship to incremental delivery
`graphql()` remains a single-result operation pipeline. A harness does not make
`graphql()` return incremental delivery payloads, and the harness `execute`
function has the same single-result contract.
Operations that use `@defer` or `@stream` should use
`experimentalExecuteIncrementally()` after parsing and validation. See
[Advanced Execution Pipelines](/docs/advanced-execution-pipelines) for the
lower-level execution APIs and [Defer and Stream](/docs/defer-stream) for the
incremental result shape.