← Blog

Supporting multiple versions of a GraphQL schema in a client-side app

Learn how GitHero uses @include and a custom validator to support both GitHub.com and Enterprise with clean, type-safe GraphQL queries.


At GitHero, we aim to deliver a smooth GitHub experience regardless of whether you're using GitHub.com or a GitHub Enterprise Server instance. One of the key technical challenges we faced was supporting both GitHub's GraphQL schema for github.com (often referred to as "dotcom") and the subtly different schemas found in various versions of GitHub Enterprise.

The Challenge

GitHub’s GraphQL API is versioned and evolves over time. The public github.com schema is always the most complete, whereas GitHub Enterprise instances lag behind depending on the version installed. This means certain fields and even entire types may be missing in an Enterprise deployment. Writing GraphQL queries that work seamlessly across both environments requires careful schema handling and strong validation.

Code Generation for Type Safety

To make our codebase maintainable and type-safe, we use graphql-codegen to generate TypeScript types from our GraphQL queries and mutations. We always run codegen against the dotcom schema. This ensures we get the most complete type definitions available, and helps us take advantage of everything dotcom supports — while being mindful of what might not exist in older schemas.

Conditional Fields with @include

To handle differences in schema support, we use the @include(if: $dotcom) directive on any field that might be missing in Enterprise versions:

query GetRepository($owner: String!, $name: String!, $dotcom: Boolean!) {
  repository(owner: $owner, name: $name) {
    id
    securityPolicyUrl @include(if: $dotcom)
  }
}

This means we can write a single query that includes extra fields if we know we're hitting dotcom — but skips them entirely if we’re using GitHub Enterprise. The best part? The generated types automatically mark those fields as undefined | T, so we don’t need to write extra guards in our TypeScript logic.

Validating the Need for @include

Over time, fields that were once only available in dotcom become available in newer GitHub Enterprise versions. To avoid overusing @include, and to ensure our queries degrade gracefully, we built a validation script that inspects our queries against the minimum Enterprise schema version we support.

How It Works

The script operates on the DocumentNode exports from the graphql.ts file that graphql-codegen generates. It uses this helper function to either strip out the fields that use @include, or just remove the directive:

// Remove directives only or the whole field
function removeDirectives(
  documentNode: DocumentNode,
  removeIncludeFields: boolean = false,
): {document: DocumentNode; removedFields: string[]} {
  const removedFields: string[] = []

  const cleanedDocument = visit(documentNode, {
    Field: node => {
      if (removeIncludeFields && node.directives) {
        const hasIncludeDirective = node.directives.some(directive => directive.name.value === 'include')
        if (hasIncludeDirective) {
          removedFields.push(node.name.value)
          return null // Remove the field
        }
      }
      return undefined
    },
    Directive: () => null, // Remove all directives
  })

  return {document: cleanedDocument, removedFields}
}

We then use GraphQL’s validate() function to run the cleaned documents against the Enterprise schema:

  • If removing the directive (but keeping the field) passes validation, it means the field is supported — and we don’t need the @include anymore.

  • If removing any fields with @include entirely causes the validation to fail, it indicates that some other part of the query is wrong, which likely means we missed an @include.

Benefits

This setup gives us the best of all worlds:

  • Single-source GraphQL queries that adapt to the backend environment.

  • Type-safe access to optional fields thanks to graphql-codegen.

  • Validation tooling that ensures correctness across schema versions.

  • Minimal runtime branching, since all the logic is embedded in the GraphQL layer.

Closing Thoughts

With an approach based on schema-aware code generation, conditional directives, and custom validation tooling, it’s possible to build robust, cross-version clients. Unfortunately, GitHub's GraphQL API doesn't expose the schema version of the server, and the @include directive doesn’t support complex conditions to target specific Enterprise versions (e.g. @include(if: $version >= something). Because of this, we cannot do queries specifically tailored for a single schema version. However by checking the queries against the latest GitHub Enterprise releases from the last six months and against dotcom, we found a balance between modern feature support and broad compatibility.