Skip to content

Schema Tuning

The generated GraphQL schema grows combinatorially with the number of tables and relation depth. A schema with 13 tables can produce anywhere from 450 to 9,000 types depending on configuration. This guide covers the knobs you can turn to keep the schema fast and manageable.

Measurements from a news application with 13 tables:

limitRelationDepthlimitSelfRelationDepthTypesSDL SizeSDL Gen Time
21~450~138 KB~40ms
31~1,100~372 KB~100ms
52~8,900~3,500 KB~350ms

Each additional depth level multiplies the number of generated types by the average fan-out of your relations.

  • Start with depth 2 for most applications. This covers the common pattern of fetching an entity with its direct relations (e.g., article with author and categories).
  • Increase to depth 3 only if your client queries genuinely need deeper nesting (e.g., article with author with organization).
  • Never use depth 5+ on schemas with many cross-references. The type explosion will slow down schema generation and increase memory usage in GraphQL tooling.
const config = {
limitRelationDepth: 2,
limitSelfRelationDepth: 1,
} satisfies BuildSchemaConfig

When specific relations cause disproportionate type expansion, prune them individually instead of reducing the global depth.

pruneRelations: {
'user.reactions': false,
}

The reactions field will not appear on the User type at all.

pruneRelations: {
'article.author': 'leaf',
}

The author field on Article will include scalar columns only — no nested relations like author.articles or author.organization.

pruneRelations: {
'attribute.asset': { only: ['selectedVariant'] },
}

The asset field on Attribute will include scalar columns plus only the selectedVariant relation.

Junction tables and audit logs rarely need mutations. Mark them as read-only to reduce the schema surface:

tables: {
config: {
articleCategory: { queries: true, mutations: false },
auditLog: { queries: true, mutations: false },
},
}

Auth tables, sessions, and other internal tables can be removed entirely:

tables: {
exclude: ['session', 'account', 'verificationToken'],
}

Excluded tables produce no types, no queries, and no mutations. Relations pointing to excluded tables are automatically skipped.

A typical production configuration uses several of these techniques together:

const config = {
mutations: true,
limitRelationDepth: 2,
limitSelfRelationDepth: 1,
suffixes: { list: 's', single: '' },
tables: {
exclude: ['session', 'account', 'verificationToken'],
config: {
articleCategory: { queries: true, mutations: false },
},
},
pruneRelations: {
'user.reactions': false,
'category.articles': 'leaf',
},
} as const satisfies BuildSchemaConfig