GraphQL vs Element API for headless Craft CMS

Craft CMS excels as a headless CMS, but one question we've seen asked multiple times is whether you should use GraphQL or Element API (JSON). This guide talks through the pros and cons of both options, and introduces the third way.

Andrew Fairlie
Written by
Andrew Fairlie
Technical Director · 13+ years with Craft CMS
Published 8 Jul 2026
Last updated 7 Jul 2026
6 min read

When you go headless with Craft CMS, one of the first decisions you'll face is how the front-end gets its content.

Craft gives you two main first-party options: a built-in GraphQL API and the Element API plugin.

They're optimised for different problems, and the trade-off almost always comes down to flexibility versus predictability, including predictable performance.

In a nut shell...

GraphQL is consumer led. There's a single endpoint with a defined a schema (with permissions of what that schema can and can't do), and then the front end asks for exactly the data it wants. Any app (eg: a Next.js site, or an iPhone app) can all hit the same endpoint and each retrieve a completely different shape of data without you touching the back end.

This puts the control on the front end developers (the consumers).

query {
  entries(section: "blog", limit: 5) {
    title
    ... on blog_blog_Entry {
      postDate @formatDateTime(format: "M j, Y")
      featuredImage { url }
      author { fullName }
    }
  }
}

Element API is provider led. A back end developer creates JSON endpoints with a fixed structure.

<?php
use craft\elements\Entry;

return [
    'endpoints' => [
        'blog.json' => function() {
            return [
                'elementType' => Entry::class,
                'criteria' => [
                    'section' => 'blog',
                    'limit' => 5,
                    'with' => ['featuredImage', 'author'], // eager-loaded
                ],
                'transformer' => function(Entry $entry) {
                    return [
                        'title' => $entry->title,
                        'postDate' => $entry->postDate->format('M j, Y'),
                        'image' => $entry->featuredImage->one()?->url,
                        'author' => $entry->author->fullName,
                    ];
                },
            ];
        },
    ],
];

In those examples, the outcome is identical. The frontend gets 5 posts from blog with a title, the post date, the image and the author.

The benefit of GraphQL

A big plus for GraphQL is that it puts front-end devs in control. They don't need backend devs to create a new API for them, so momentum is kept.

When a designer decides the blog cards now need an excerpt and a reading-time estimate, the front-end developer just adds those fields to the query and ships.

That compounds if the CMS is powering multiple apps such as the website and an iPhone app. Each can request slightly different things against the same schema, no backend work required.

GraphQL is also intuitive for deeply relational content. For example you can fetch an entry, its related categories, the assets on those categories, and the author's other posts all in one request.

The benefit of Element API

Element API's fixed endpoints might feel restrictive compared to GraphQL, and they are - intentionally.

That rigidity buys you a lot of stability and control. Every endpoint has a known, stable URL and a known, stable response - well suited to unit tests. You decide the exact query criteria, you decide the eager-loading, and you decide the output shape, and how it should be cached. There are no surprises about what the database is being asked to do, because you wrote the request, not the client.,

This makes Element API a natural fit for well-defined and stable data requests: a list of blog posts, a product feed, a sitemap, a search index export.

Anywhere the shape of the data is genuinely known in advance and unlikely to change per-client, the "limitation" of a fixed endpoint is really a guarantee.

The rub: performance

GraphQL's flexibility is also its performance risk. Because the client composes the query, the client can compose a slow one.

Deeply nested relational queries can trigger N+1 problems if fields aren't eager-loaded well, and a single ambitious query can fan out into a surprising amount of database work. Caching is trickier too: responses vary by query shape, and queries are conventionally sent as POST requests, which don't cache at the HTTP/CDN layer the way plain URLs do.

Craft mitigates this with built-in GraphQL result caching and lets you send queries over GET for CDN caching, but you have to be deliberate about it. In practice you'll also want to lean on query-complexity limits and schema scoping so a client can't accidentally (or maliciously) ask for the entire content tree in one go.

Also, as your CMS schema (more sections, sub-sites, etc.) gets more complicated, the performance of GraphQL is known to drop quite considerably.

Element API's rigidity is its performance advantage. Because you author the query server-side, you control eager-loading yourself, and you can tune each endpoint until it's exactly as efficient as it needs to be.

Every endpoint is a GET URL returning a stable shape allowing it to cache efficiently via a reverse proxy or in Craft's own cache.

Put simply: GraphQL's performance depends on how well both sides behave, while Element API's performance is something you can nail down entirely on the server.

So, which to use?

Consider GraphQL when:

  • Your front-end requirements are evolving quickly and you want to iterate without back-end deploys.
  • You have multiple consumers with different data needs.
  • Your content is highly relational and you value fetching nested data in one request.
  • Your team is comfortable owning query complexity, caching strategy, and schema scoping.

Consider Element API when:

  • Your data contracts are stable and well understood up front - perhaps you have third parties using it as an API.
  • Predictable performance and straightforward edge/CDN caching are priorities.
  • You want tight, explicit control over exactly what queries run against your database.
  • You're feeding a specific, structured consumer like a static build, a feed, or a search index.

It's not actually a choice...

Really though, Craft isn't doing anything to stop you using both at the same time. Or even throw in some custom controllers for CRUD operations as a third-way.

Plenty of Craft builds expose GraphQL for the flexible, exploratory parts of the front end while serving a handful of performance-critical routes through highly optimised Element API endpoints.

Use GraphQL where flexibility earns its keep, and fall back to Element API where a stable, cacheable, server-controlled contract matters more than letting the client improvise.

The framing that's served me best: GraphQL optimises for developer flexibility; Element API optimises for operational predictability. Decide which one your particular endpoint needs more, and the choice tends to make itself.

One last point: I ran a project where we initially used GraphQL for everything which let us build with momentum but then used an LLM (Claude Code) to create Element API endpoints to replace each of our GraphQL requests once we were comitted to the data structure. It gave us the fluidity to play but when they time came to commit to performance, we could swap over to Element API quickly to reap the rigidity rewards.

Andrew Fairlie
Andrew Fairlie
Technical Director · 13+ years with Craft CMS

Andrew is Technical Director at Mutual, a Craft CMS Partner agency. He has been building with Craft CMS since its public beta in 2012 — working through every major version from Craft 1 to Craft 5 — and has delivered over 100 sites for clients including Apple, Transparency International, and Arts University Bournemouth.

He writes about Craft CMS on the Mutual blog and has contributed to net Magazine. At Mutual, he leads development of Mutual One, a marketing platform built on Craft CMS as its foundation.

He has spoken about Craft CMS to undergraduate students at the University of Brighton and Canterbury Christ Church University, and appeared on the Devmode.fm podcast. He has also trained development teams at other agencies in working with the platform.

Further reading

Craft CMS
How To Improve Craft CMS Search Results
7 min
Craft CMS
The History of Craft CMS: From Blocks to Laravel
7 min
Craft CMS
Craft CMS 5.10: Safer Deletes, Time Zones, and an Important Security Fix
4 min

Let's get your site
where it should be

Emma Andrew

Start a conversation with Emma and Andrew

Emma, Operations Director · Andrew, Technical Director

hello@mutual.agency