---
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.