AEM Headless CMS: Architecture, GraphQL, and Implementation
Content Fragments, GraphQL delivery, Universal Editor, and the practical tradeoffs of going headless with Adobe Experience Manager.
How AEM Headless Architecture Works
Your content doesn't have to be tied to a single website. By keeping core content in Adobe Experience Manager (AEM) Content Fragments and exposing it headlessly, you get a reusable content backbone that can feed websites, apps, chats, and anything else that can talk to an API.
This guide covers the full picture: how the headless architecture fits together, how to model and query content with GraphQL, implementation steps for a real app, delivery API options, where Universal Editor fits, and the honest tradeoffs you should weigh before committing.
AEM's headless model separates content from presentation at the platform level:
- Content Fragments — structured, channel-neutral content authored in AEM and stored as JSON-ready assets. Each fragment is based on a Content Fragment Model that defines its fields, types, and relationships.
- GraphQL API (persisted queries) — AEM exposes persisted GraphQL queries as stable HTTP GET endpoints. Any front end can fetch exactly the fields it needs, nothing more. Persisted queries are the recommended delivery method for Publish; ad hoc POST requests are blocked by default in standard Dispatcher configurations.
- OpenAPI delivery and management — AEM exposes two distinct OpenAPI layers. The Content Fragment Delivery OpenAPI is optimised for live content delivery with CDN integration. The Content Fragment and Content Fragment Model Management OpenAPIs handle author-side CRUD operations. See the delivery API section below for when to use each.
- "Head" — the rendering layer: a Next.js app, a React SPA, a mobile app, an Edge Delivery Services site, Adobe Journey Optimizer, or any other consumer.
The result is a single source of truth: authors maintain content once in AEM, and multiple channels consume it independently.
Content Fragment Models: defining your schema
Before authors can create fragments, architects define Content Fragment Models — the schema for each content type. A model specifies:
- Field names, types (text, rich text, number, boolean, date, reference), and whether they are required or optional.
- Fragment references (relations between content types, e.g. an Article referencing an Author).
- Enumerations, default values, and tab groupings for the authoring UI.
Keep models focused — one content type per model rather than a single mega-model with optional fields for every use case. Avoid excessive nesting of Content Fragment references; deep reference chains make queries more expensive and harder to cache effectively. Adobe recommends creating only as many models as genuinely needed, keeping structures simple and well-bounded.
Querying Content with GraphQL
For production delivery, AEM requires persisted queries: GraphQL queries saved in AEM and exposed as stable GET endpoints. Ad hoc POST requests sending a query body are intended for development and exploration only; they are not cached and are blocked on standard Dispatcher/CDN configurations.
Persisted query endpoints follow the pattern:
https://publish-pXXXX-eYYYY.adobeaemcloud.com/graphql/execute.json/<configuration>/<query-name>
Here, <configuration> is the name of your GraphQL configuration (typically the root path of your content tree), and <query-name> is the name you gave the query when you persisted it in AEM. Query variables are appended as URL-encoded parameters in the request URL.
Calling a persisted query from a Next.js app
The example below uses the App Router with fetch and Next.js cache control. If you are using the Pages Router, the same fetch call works inside getServerSideProps (SSR) or getStaticProps with revalidate (ISR) — name the routing model you are targeting so your caching strategy is explicit.
// App Router — server component or route handler
async function fetchArticles() {
const url = `${process.env.AEM_PUBLISH}/graphql/execute.json/my-project/article-list`;
const response = await fetch(url, {
// Next.js cache option; adjust revalidation to match your content freshness needs
next: { revalidate: 60 },
});
if (!response.ok) {
throw new Error(`AEM GraphQL request failed: ${response.status}`);
}
return response.json();
}
article-list is the name of a persisted query authored and published in AEM. The endpoint caches well at the CDN because the URL is stable — but actual caching behaviour depends on how your AEM environment and CDN are configured; a stable URL is a necessary condition, not a sufficient one.
Example persisted query: fetch a list of articles
Author this in the AEM GraphQL Query Editor (see the implementation steps section) and persist it as my-project/article-list:
{
articleList {
items {
_path
title
summary
publishDate
author {
name
role
}
heroImage {
... on ImageRef {
_path
mimeType
width
height
}
}
}
}
}
For large result sets, apply pagination and filtering within the query rather than fetching all fragments and filtering on the client. AEM's GraphQL layer evaluates filters after loading all fragments that share a model into memory, so an unfiltered list query can be expensive even if only a handful of results are returned to the client.
Example persisted query: fetch a single article by path
{
articleByPath(_path: "/content/dam/my-site/articles/my-article") {
item {
_path
title
body {
html
plaintext
}
tags
}
}
}
Implementation: step by step
The following sequence covers the author → preview → publish flow. Each step happens in a specific AEM environment — keep these distinct:
- Define Content Fragment Models on AEM Author via Tools → Assets → Content Fragment Models. One model per logical content type. Keep models simple; excessive nesting of references makes queries harder to maintain and cache.
- Create Content Fragments on AEM Author in the Assets or Sites console.
- Preview and publish Content Fragments. Fragments must be published to AEM Publish (or Preview for staging) before they appear via the GraphQL API. Publishing is an explicit author action; saving a fragment does not make it available to the delivery endpoint.
- Configure and publish a GraphQL endpoint on AEM Author via Tools → Assets → GraphQL Endpoints. Endpoints must also be published — unpublished endpoints return 404 from Publish.
- Author and test persisted queries using the GraphQL Query Editor at Tools → General → GraphQL Query Editor (or
/aem/graphiql.htmlon Author). Configure your endpoint in the IDE before writing queries — the IDE requires a published endpoint to execute against. Save queries as persisted queries once you are satisfied. - Publish persisted queries from the GraphQL Query Editor or via the configuration replication. Like fragments and endpoints, persisted queries must be published to be available on Publish.
- Call persisted query endpoints from your front end using GET requests (see the GraphQL section above). Your application should always call AEM Publish (or AEM Preview for staging); never call Author from a public-facing app.
- Instrument for Universal Editor (optional) — add
data-aue-*attributes to your rendered HTML so authors can edit content in-context. See the section below.
Delivery API options: GraphQL vs OpenAPI
AEM as a Cloud Service exposes three distinct structured-content APIs. They serve different purposes and are not interchangeable:
Cross-Channel Reuse and Governance
Keeping content in Content Fragments has concrete operational benefits beyond API delivery:
- Cross-channel reuse — the same fragment can be used on the website, in a native app, in Adobe Journey Optimizer messages, or other downstream systems. One maintained record, many consumers. Content Fragments Overview
- Structured modeling — Content Fragment Models let architects define exactly which fields exist, so developers can query only what they need and authors don't have to worry about JSON structure. Headless Content Delivery using Content Fragments with GraphQL
- Localization and governance — AEM's author/preview/publish model, workflows, and translation tools all apply to fragments. You get version history, approvals, and translation flows even if the final experience is rendered by a custom framework. Getting Started with AEM Headless as a Cloud Service
Universal Editor: in-context editing for headless apps
Headless setups traditionally had a trade-off: great developer flexibility, weaker authoring experience. AEM's Universal Editor closes that gap by providing in-context editing for headless and hybrid apps.
When a site or app is instrumented for Universal Editor:
- The editor loads your app in a canvas and reads
data-aue-*attributes to identify editable regions and their backing content in AEM Author. - Authors click directly on rendered content to edit the underlying fragment fields — even inside a decoupled Next.js or React app.
- Edits are saved to AEM Author. Publishing the updated content to Preview or Publish is a separate, explicit author action — saving does not automatically make changes live.
<!-- Connection meta tag: note the urn:adobe:aue:system: prefix -->
<meta
name="urn:adobe:aue:system:aemconnection"
content="aem:https://author-pXXXX-eYYYY.adobeaemcloud.com"
/>
<script src="https://universal-editor-service.adobe.io/cors.js" async></script>
export function ArticleTeaser({ fragmentPath, title }) {
return (
<h2
data-aue-resource={`urn:aemconnection:${fragmentPath}`}
data-aue-type="content-fragment"
data-aue-prop="title"
>
{title}
</h2>
);
}
For a complete walkthrough with a working demo, see Integrating AEM Universal Editor with a Next.js App.
Getting Started with the Universal Editor in AEM · Attributes and Types
Tradeoffs: when headless is and isn't the right call
When headless with Content Fragments is the right choice
- You need the same content in multiple places: web, mobile apps, email, in-product surfaces.
- You want strong governance, localization, and workflow around that content.
- You're investing in Universal Editor or other in-context authoring for headless experiences.
- You expect to plug in additional "heads" — new sites, applications, or channels — over time without redoing the content layer.
- Your content has rich structure: relationships, taxonomies, versioning requirements that a document model can't express cleanly.
When to consider an alternative
- Pure marketing pages with no cross-channel reuse: Edge Delivery Services with Document Authoring requires less infrastructure to operate and can deliver excellent performance for marketing content — without the overhead of Content Fragment Models, persisted queries, and GraphQL endpoint management.
- Simple microsites with a single author: the overhead of content modeling, API configuration, and environment management may outweigh the flexibility benefits.
- Teams without AEM expertise: headless AEM requires solid architecture and DevOps investment. Without it, the extra complexity adds fragility rather than flexibility.
Working examples and related labs
- AEM Universal Editor with a Next.js App — full walkthrough with a live demo at wknd.edgepatterns.dev.
- Editing AJO Emails with Universal Editor — POC using AEM Content Fragments, Universal Editor, and the AJO API.
- AJO Code-Based Experience on EDS — connects AJO personalization to an EDS page via Web SDK.
For the broader architecture context see Experience Delivery Foundation Overview.