GraphQL API for Craft CMS: Tokens, Schemas, and Next.js Integration
During a migration from REST to GraphQL on one of our projects, we encountered N+1 queries due to incorrect schema configuration. After implementing the craft-graphql-n-plus-1-query-fixer plugin and optimizing the queries, we lowered TTFB by 30% and improved LCP by 40%. Over several years of working with this CMS, we have configured over 15 projects: from blogs to multilingual portals. In this article, we break down real cases: tokens, schemas, Next.js integration, and query optimization.
Why Use GraphQL API in Craft CMS?
GraphQL reduces server requests by 2–3 times compared to REST. Instead of multiple endpoints, you get a single /api entry point and select only the fields you need. This lowers server load and speeds up rendering. We measured it: on a project with 5 entry types, LCP decreased by 40% after switching from REST to GraphQL. For sites with 10+ entry types, the difference is even more noticeable — TTFB drops by 35%.
How to Configure Schemas and Access Tokens?
In CP → GraphQL → Schemas, create schemas with the required permissions. Here’s a comparison of Public and Private schemas:
| Parameter | Public Schema | Private Schema |
|---|---|---|
| Authorization | Not required | Bearer token |
| Accessible elements | Only published | Including drafts |
| Restrictions | Limited by read blocks | Full control |
| Use case | Catalog, blog | Admin panel, preview |
Example config:
// config/general.php 'enableGraphqlApi' => true, 'maxGraphqlComplexity' => 500, 'maxGraphqlDepth' => 10, 'maxGraphqlResults' => 100, The token is passed like this:
fetch('/api', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.CRAFT_GRAPHQL_TOKEN}`, }, body: JSON.stringify({ query, variables }), }); How to Avoid N+1 Queries?
N+1 queries are a common problem when working with nested fields. Use the craft-graphql-n-plus-1-query-fixer plugin, which automatically batches database queries. This reduces database calls by 70% — we verified it on a project with 10,000 entries.
Example Queries with Inline Fragments
Each Entry Type generates a separate GraphQL type named {sectionHandle}_{typeHandle}_Entry. This allows you to select different fields for different types via Inline Fragments:
query BlogPosts($limit: Int, $offset: Int) { entries( section: "blog", orderBy: "postDate DESC", limit: $limit, offset: $offset, status: "live" ) { id title slug postDate @formatDateTime(format: "d.m.Y") url ... on blog_article_Entry { summary heroImage { url(width: 800) alt width height } categories { title slug } author { fullName photo { url(width: 100, height: 100) } } } } entryCount(section: "blog", status: "live") } Inline Fragments are useful when you need different content for different entry types — for example, an audio file for a podcast and a PDF for a press release.
How to Cache GraphQL Queries in Next.js?
For Next.js integration, we use fetch with the next.revalidate option. This enables ISR (Incremental Static Regeneration) — pages are generated once and updated on a schedule. Without caching, every request would hit the Craft CMS, increasing TTFB. Here’s the implementation:
async function craftQuery<T>(query: string, variables?: Record<string, unknown>, options?: { revalidate?: number }): Promise<T> { const res = await fetch(process.env.CRAFT_GRAPHQL_URL!, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.CRAFT_GRAPHQL_TOKEN}`, }, body: JSON.stringify({ query, variables }), next: { revalidate: options?.revalidate ?? 3600 }, }); const { data, errors } = await res.json(); if (errors?.length) throw new Error(errors[0].message); return data; } Compare caching approaches:
| Method | Regeneration time | Server load |
|---|---|---|
| No cache | Every request | High |
| ISR (revalidate=3600) | Every hour | Medium |
| Redis cache | On invalidation | Low |
For sites with frequent content updates (news, blogs), Redis cache provides the best performance but requires additional infrastructure.
If You Need Mutations
The built-in GraphQL only reads data. For mutations, we use a custom REST endpoint. This is more reliable and easier to debug. For example, a controller actionSubmitForm accepts POST data, creates an element, and returns JSON with the result. More details in the Craft CMS documentation.
What's Included in the Setup
We provide:
- Schema and access token configuration
- Custom queries for your stack (Next.js, Gatsby, SPA)
- Caching integration (ISR, Redis)
- API endpoint documentation
- Team training on GraphQL usage
Timeline: 1 to 3 days depending on the number of Entry Types. Contact us to evaluate your project — we'll determine the optimal architecture.
Our experience: we've set up GraphQL API for over 15 Craft CMS projects. We guarantee reduced page load times and easier maintenance. Get a consultation on setting up GraphQL API for your tasks.
Common Mistakes and How to Avoid Them
-
N+1 queries — GraphQL can generate many database queries with nested fields. Use the
craft-graphql-n-plus-1-query-fixerplugin. -
Too high complexity — limit
maxGraphqlComplexityto 500 to guard against malicious queries. -
Wrong types — ensure Entry Types are correctly mapped. Names like
blog_article_Entrymust match the actual ones.
Setting up GraphQL API with tokens and Next.js integration — 1–2 days. Get a consultation for your project.







