← Back to list

AEM Headless Delivery, Round Two: GraphQL vs REST OpenAPI for Content Fragments

Albin Issac in Tech Learnings · 2026-08-03 23:53 · 0 claps · 5.7 min read
#web-development #website #cms #headless #api
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval 🌐 · Web Development

AEM Headless Delivery, Round Two: GraphQL vs REST OpenAPI for Content Fragments

A while back I wrote about delivering headless content through the GraphQL API and Content Fragments in AEM. That post is still the one to read if you want the full walkthrough — Content Fragment Models, enabling GraphiQL, persisted queries, CORS.

Since then, Adobe has kept building out the API surface, and if you’ve gone looking at AEM’s docs recently you’ve probably hit the same wall I did: you open the headless docs expecting one API and find GraphQL, a “REST Open API,” and then — confusingly — GraphQL operations inside that Open API too. It looks like three overlapping things. It’s actually two APIs, documented in a way that makes them look like three.

This post is the missing half: what the REST option actually is, how it differs from GraphQL in practice, and a straight answer to “which one do I use” — including the case that made me pick one over the other for a real project.

The landscape, briefly

Strip away the naming noise and AEM’s headless surface for Content Fragments breaks into three real API families:

  1. Traditional GraphQL — the one from my earlier post. A query language over a schema generated from your Content Fragment Models.
  2. Content Fragment Delivery OpenAPI — a plain REST API. GET a fragment, get its JSON back. No query language.
  3. Sites API (/adobe/sites) — a separate, gateway-style API that happens to expose both a REST surface (Content Fragment and Model management — create/edit/delete) and a GraphQL surface (/cf/graphql), bundled under one OpenAPI spec with one auth rule: every single operation requires a bearer token, author or publish, no exceptions. This is the management side of the house, not delivery, and it's why you see "GraphQL" show up a second time inside something labeled "Open API" — it's not a different querying mechanism, it's the same GraphQL query language wrapped in a different, always-authenticated door.

That third one is a story for another post. For pure content delivery — which is what most frontends, mobile apps, and edge functions actually need — you’re choosing between #1 and #2. That’s the comparison this post is about.

Option 1: GraphQL Delivery (recap)

Covered in depth in the earlier post, so just the essentials here:

  • Schema is auto-generated from your enabled Content Fragment Models.
  • Two ways to call it:
  • Persisted queries (GET /graphql/execute.json/<configName>/<queryName>) — cached by Dispatcher and CDN. This is the production path.
  • Ad-hoc queries (POST with the raw query in the body) — not cached, blocked by default Dispatcher config, meant for GraphiQL exploration and dev/testing only.
  • Auth: open by default. The GraphQL endpoint, once created, is “accessible to everyone” per Adobe’s own docs — you’re expected to lock it down with ACLs (and CUGs, if you need per-content restrictions) rather than it being closed by default.
  • Enablement: fully self-service. An author or admin creates the endpoint in AEM’s Tools UI. No Adobe involvement.

Option 2: Content Fragment Delivery OpenAPI

This is the newer, REST-shaped counterpart. No query language — you hit a resource URL and get that fragment’s JSON, shaped by its Content Fragment Model.

# Get a fragment by ID
curl "https://<your-publish-host>.adobeaemcloud.com/adobe/contentFragments/{fragmentId}"
# Get a fragment by path instead of ID
curl "https://<your-publish-host>.adobeaemcloud.com/adobe/contentFragments/byPath?path=/content/dam/my-site/en/articles/my-article"
# List all fragments for a given model
curl "https://<your-publish-host>.adobeaemcloud.com/adobe/contentFragments/models/{modelId}/fragments"
# Follow references from one fragment to related fragments
curl "https://<your-publish-host>.adobeaemcloud.com/adobe/contentFragments/{fragmentId}/references"

A few things worth knowing before you build on this one:

  • Auth: open by default on Publish — Adobe states plainly that “the AEM Publish service does not require authentication” for this API. Preview is the same, but only requires a bearer token if you’ve explicitly opted into restricting it. You can layer on an AEM CDN Edge key if you want a lightweight access gate without full user-based auth.
  • Caching: every operation is a GET, so it's cacheable at Dispatcher/CDN the same way any REST resource would be — no persisted-query registration step required.
  • Enablement is the gotcha: unlike GraphQL, this API is not on by default. You have to file an Adobe Support ticket — titled “Enable Content Fragment Delivery with OpenAPI,” naming your program and environment IDs — before it works at all. Budget for that lead time; it’s not a flag you flip yourself in Cloud Manager.
  • It also gives you an HTML rendering endpoint (/{templateId}/{fragmentId}/{variation}.html) if you ever want server-rendered fragment output rather than JSON, which GraphQL has no equivalent for.

Side by side

GraphQL DeliveryREST Delivery OpenAPIQuery mechanismGraphQL query languagePlain resource URLs, no query languageResponse shapeExactly the fields you ask forFull fragment shape, as modeledMulti-fragment compositionOne request, server-side resolution of referencesOne request per fragment (client stitches results, or you crawl /references)CachingPersisted queries (GET) cached; ad-hoc (POST) not cachedEvery call is GET, cached by defaultAuth on PublishOpen by default (lock down via ACLs/CUGs)Open by default (lock down via CDN Edge key)EnablementSelf-service, in AEM ToolsRequires an Adobe Support ticket per environmentVersioned per-client contractsYes — persisted queries are named and can be pinned per app releaseNo — response shape changes globally when the model changes

So which one do you actually use?

The caching asymmetry is worth calling out on its own. REST Delivery OpenAPI is cacheable by default — every call is a GET, full stop, so Dispatcher/CDN caching applies from the first request with no extra setup. GraphQL's cacheability is opt-in: only persisted queries are cacheable; any query sent as raw text (the ad-hoc POST form) bypasses caching entirely. That's not a minor footnote — it's why ad-hoc GraphQL isn't a production delivery path, only a dev/testing convenience. If you adopt GraphQL and skip the persisted-query registration step, you don't get REST's "cacheable by default" behavior for free — you get uncached, unauthenticated-by-default traffic hitting AEM directly on every request.

If you’re serving something simple and flat — a single fragment lookup, a list of one model type, a config blob — REST Delivery OpenAPI is the lower-friction choice. No query to author, no persisted-query publish cycle, the response shape just tracks the model automatically.

The moment you need to compose several models into one response — and especially if you’re bandwidth- or latency-sensitive — GraphQL earns its extra setup cost back quickly.

Case study: picking one for a mobile app

Here’s the situation that made this concrete for me: a mobile app pulling Content Fragments across multiple models — think product info, a related-items rail, a promo banner, and a reviews snippet, all on one screen, each backed by a different model.

Three things pushed the decision toward GraphQL persisted queries:

  1. Composition. Screens like that need several models stitched together. With REST Delivery, that’s one call per fragment plus client-side assembly. With GraphQL, it’s one query, resolved server-side.
  2. Payload discipline. Mobile clients pay for every extra byte in latency, data cost, and battery. REST always returns the full fragment as modeled; GraphQL returns only the fields the screen actually renders.
  3. Release discipline. This is the one that’s easy to miss. Mobile apps don’t deploy instantly — users sit on old builds for weeks after a release. Persisted queries let you register getProductScreenV1, ship getProductScreenV2 for the next release, and never break the installed base still calling V1. REST Delivery has no equivalent: change the model, and every consumer's response shape changes at once, whichever app version they're running.

None of that makes GraphQL the universally “right” answer — for a simple settings fragment or a flat content list, it’d be overkill. But for a multi-model, release-versioned mobile client, the persisted-query workflow is doing exactly the job it was built for.

Practical checklist before you commit

  • Going REST Delivery OpenAPI? File the Adobe Support enablement ticket early — it’s the one step with a lead time outside your control.
  • Going GraphQL? Decide your persisted-query naming convention up front (per screen, per app version, or both) before you register your first one — retrofitting a naming scheme after several queries are in production is more painful than it sounds.
  • Either way, don’t ship on the open-by-default Publish behavior without a deliberate decision — confirm with whoever owns the AEM instance whether ACLs (GraphQL) or a CDN Edge key (REST) should be gating access, rather than inheriting “open” by accident.

메타데이터
post_id
ff0285b4f5ff
slug
aem-headless-delivery-round-two-graphql-vs-rest-openapi-for-content-fragments-ff0285b4f5ff
url
https://medium.com/tech-learnings/aem-headless-delivery-round-two-graphql-vs-rest-openapi-for-content-fragments-ff0285b4f5ff
canonical_url
https://medium.com/tech-learnings/aem-headless-delivery-round-two-graphql-vs-rest-openapi-for-content-fragments-ff0285b4f5ff
author_url
https://medium.com/@techforum
status
ok
fetched_at
2026-09-03 18:34:02