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