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.
Impact of relation depth
Section titled “Impact of relation depth”Measurements from a news application with 13 tables:
limitRelationDepth | limitSelfRelationDepth | Types | SDL Size | SDL Gen Time |
|---|---|---|---|---|
| 2 | 1 | ~450 | ~138 KB | ~40ms |
| 3 | 1 | ~1,100 | ~372 KB | ~100ms |
| 5 | 2 | ~8,900 | ~3,500 KB | ~350ms |
Each additional depth level multiplies the number of generated types by the average fan-out of your relations.
Recommendations
Section titled “Recommendations”- 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 BuildSchemaConfigUsing pruneRelations
Section titled “Using pruneRelations”When specific relations cause disproportionate type expansion, prune them individually instead of reducing the global depth.
Remove a back-reference entirely
Section titled “Remove a back-reference entirely”pruneRelations: { 'user.reactions': false,}The reactions field will not appear on the User type at all.
Stop expansion at one level
Section titled “Stop expansion at one level”pruneRelations: { 'article.author': 'leaf',}The author field on Article will include scalar columns only — no nested relations like author.articles or author.organization.
Whitelist specific child relations
Section titled “Whitelist specific child relations”pruneRelations: { 'attribute.asset': { only: ['selectedVariant'] },}The asset field on Attribute will include scalar columns plus only the selectedVariant relation.
Making tables read-only
Section titled “Making tables read-only”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 }, },}Excluding internal tables
Section titled “Excluding internal tables”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.
Combining strategies
Section titled “Combining strategies”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