Custom Transforms

Custom Transforms

Custom transforms are fairly straightforward to write. They are simply objects with up to three methods:

  • transformSchema: receives the original subschema and applies modifications to it, returning a modified wrapper (proxy) schema. This method runs once while initially wrapping the subschema.
  • transformRequest: receives each request made to the wrapped schema. The shape of a request matches the wrapper schema and must be returned in a shape that matches the original subschema.
  • transformResult: receives each result returned from the original subschema. The shape of the result matches the original subschema and must be returned in a shape that matches the wrapper schema.

The complete transform object API is as follows:

export interface Transform<T = Record<string, any>> {
  transformSchema?: SchemaTransform
  transformRequest?: RequestTransform<T>
  transformResult?: ResultTransform<T>
export type SchemaTransform = (
  originalWrappingSchema: GraphQLSchema,
  subschemaConfig: SubschemaConfig,
  transformedSchema?: GraphQLSchema
) => GraphQLSchema
export type RequestTransform<T = Record<string, any>> = (
  originalRequest: Request,
  delegationContext: DelegationContext,
  transformationContext: T
) => Request
export type ResultTransform<T = Record<string, any>> = (
  originalResult: ExecutionResult,
  delegationContext: DelegationContext,
  transformationContext: T
) => ExecutionResult
type Request = {
  document: DocumentNode
  variables: Record<string, any>
  extensions?: Record<string, any>

A simple transform that removes types, fields, and arguments prefixed by an underscore might look like this:

import { stitchSchemas } from '@graphql-tools/stitch'
import { filterSchema, pruneSchema } from '@graphql-tools/utils'
class RemovePrivateElementsTransform {
  transformSchema(originalWrappingSchema) {
    const isPublicName = name => !name.startsWith('_')
    return pruneSchema(
        schema: originalWrappingSchema,
        typeFilter: typeName => isPublicName(typeName),
        rootFieldFilter: (operationName, fieldName) => isPublicName(fieldName),
        fieldFilter: (typeName, fieldName) => isPublicName(fieldName),
        argumentFilter: (typeName, fieldName, argName) => isPublicName(argName)
  // no need for operational transforms
const subschema = {
  schema: myRemoteSchema,
  transforms: [new RemovePrivateElementsTransform()]
const gatewaySchema = stitchSchemas({ subschemas: [subschema] })