Now booking enterprise content platform builds for 2026. Contact us

All articles Migrations 12 min read

Migrating from AEM to Payload CMS: costs, steps, and trade-offs

Adobe Experience Manager's licensing and operational costs lead many mid-market teams to look at alternatives. This guide covers how to migrate from AEM to Payload CMS: what it costs, how long it takes, the step-by-step technical path, and the trade-offs the vendor pages skip.


Mid-market teams rarely leave Adobe Experience Manager over a single complaint. They leave when the licensing and operational costs stop making sense, releases slow down, and every front-end change has to route through a platform built for an earlier era of the web.

If that sounds like your situation, Payload CMS is one of the alternatives worth a serious look. It’s open-source, code-first, and built on the same stack a modern product team already uses: TypeScript, Node.js, React, and Next.js. This guide covers why teams make the move, what it actually costs, how the migration works step by step, and the parts that the sales pages tend to skip.

Why teams are leaving AEM

Teams leave AEM for a handful of reasons, and they show up consistently across migration accounts from agencies that do this work, including FocusReactive and WegWorks.

  • Licensing cost.
    AEM is a commercial enterprise platform, and the price reflects that. We put numbers on this below.
  • Operational weight.
    AEM runs on a Java and OSGi architecture with its own content repository, and keeping it healthy takes dedicated DevOps. Release cycles stretch because the platform is large and the moving parts are many.
  • Backend customisation.
    Extending AEM tends to require AEM specialists and Adobe-specific tooling, which narrows your hiring pool and lengthens delivery.
  • Developer experience.
    Teams used to modern JavaScript frameworks often find AEM's authoring and development model slow to work in, and that friction compounds the customisation problem above.
  • Ownership.
    Self-hosting an open-source CMS means your code, your data, and your infrastructure are yours, with costs you can predict.

AEM is a capable platform. It’s also a heavy one, and the question for your team is whether you need the weight.

AEM 6.5 support ends on two different dates

If a support deadline is what put this migration on your roadmap, check which deadline applies to you first. Adobe publishes two, and the one you get depends on how your instance is hosted.

The statement sits on Adobe’s Experience Manager releases roadmap, last updated May 7, 2026.

Your deploymentAdobe’s published timeline
Adobe Managed ServicesSupport “ends by August 31, 2026”
On-premiseCore support “currently planned to end by February 2027”

The same page notes that its dates “are for information only and are subject to change at any time,” so confirm yours against your own contract. The AEM 6.5 LTS FAQ puts the on-premise date exactly: Adobe will continue to support AEM 6.5 until February 28, 2027.

The roadmap adds the service-pack mechanics, and they are worth reading closely. Each service pack is supported for up to 18 months from its release, capped at February 28, 2027, and 6.5.26.0, targeted for November 19, 2026, is the last supported service pack on the 6.5 branch. That final service pack therefore carries a little over three months of cover, not the nominal eighteen.

What the date actually changes. Service packs are how fixes and security patches reach the 6.5 branch, so the practical effect of the branch closing is that 6.5.26.0 is the last one Adobe ships for it. Neither the roadmap nor the LTS FAQ says how Adobe handles support tickets once your date has passed, and neither describes a paid extended-support arrangement for 6.5. Both of those sit in your agreement rather than in the public documentation, so get them from your Adobe representative in writing before you plan around either. If you are required to run vendor-supported software on a current patch path, the date is when 6.5 stops meeting that requirement.

Who it affects. Teams on AEM 6.5, on either hosting model. AEM as a Cloud Service is a rolling release rather than a versioned branch, so it carries no end-of-support date of this kind, though Adobe does require Cloud Service programs to stay on the current release. Adobe’s roadmap publishes one set of 6.5 timelines and does not break them out by Sites, Assets or Forms. The only Forms-specific note on the page is that Forms add-on packages ship one week after the matching service pack.

Adobe’s own answer is 6.5 LTS. This is worth stating plainly, because it changes the shape of the decision. Asked what happens to an on-premise customer who does not upgrade, Adobe’s FAQ answers that “it is recommended that organizations plan for an upgrade to 6.5 LTS,” and the roadmap says both the Managed Services and the on-premise paths are covered by that branch. AEM 6.5 LTS carries Java 17 and Java 21 support and is, in Adobe’s words, the branch that “will be the AEM branch that receives new AEM features and innovations.” You can move from 6.5 to any 6.5 LTS service pack directly. Adobe flags that some legacy features and artifacts have been removed, and points at its AEM Analyzer tool to size the impact. Neither page we checked gives 6.5 LTS an end-of-support date. So the deadline forces a decision about which AEM branch you run. Whether you stay on AEM at all is the question the rest of this guide is about.

Put it against an 8 to 12 week migration. The timeline further down this page is 8 to 12 weeks. Starting in late July 2026, that finishes between roughly September 20 and October 18. August 31 is about five weeks out, so a Managed Services team cannot migrate to Payload ahead of its date, and compressing the plan to fit would mean cutting the audit and integration work that these projects actually spend their weeks on. If that is your situation, separate the two moves: settle your AEM position first, on 6.5 LTS or whatever your agreement provides, then run the migration on a schedule that lets you do it properly. The February 2027 on-premise date reads differently. Seven months leaves room for the audit, the 8 to 12 week build, and a cutover with time in hand for the parts that run late.

AEM and Payload, side by side

We drew this comparison from the WegWorks feature table and the FocusReactive feature mapping, and it holds up against the platforms’ own documentation.

DimensionAEMPayload CMS
LicensingCommercial, high (see costs below)Open-source, MIT Licence
StackJava, OSGi, HTLTypeScript, Node.js, React/Next.js
APIPartial (Content Services, GraphQL)Native REST and GraphQL
Content modellingUI and configuration heavyCode-first, versioned, testable
DatabaseJCR (Apache Oak)MongoDB or PostgreSQL
HostingAdobe Cloud Service or on-premiseAny Node host, Docker, serverless
Best fitFull digital experience platform inside the Adobe stackContent and marketing sites, custom backends

The factor that decides most of these projects is not in the table above. AEM’s strength is its tight integration with the rest of Adobe’s tools: Adobe Target for personalisation, Adobe Analytics for measurement, Creative Cloud for assets. If your marketing operation runs on those, that integration is what you would be giving up, and rebuilding it is part of the migration cost. The section on what you have to rebuild covers this in detail.

For content and marketing sites that don’t depend on the full Adobe stack, Payload removes cost and complexity while handing engineers direct control. That is the case where it fits best.

What the move actually costs

Adobe does not publish list pricing for AEM, so every figure here is an industry or agency estimate. Treat them as ranges to validate against your own quote, not as Adobe’s official numbers.

The estimates that recur across migration write-ups put AEM Sites licensing starting around $60,000 per year. Full AEM as a Cloud Service deployments are commonly cited in the $200,000 to $300,000-plus per year range. Implementation typically adds $500,000 to $1,000,000, and Adobe support contracts add roughly 15 to 25 percent of licensing on top.

Payload is free to license under the MIT Licence. Your costs are hosting and engineering. Self-hosting runs from roughly $10 per month on a Docker VPS to a few hundred per month on a cloud provider like AWS, depending on scale and compliance needs.

One caveat that older guides get wrong. Payload Cloud, the project’s managed-hosting option, has paused new sign-ups since the Figma acquisition. If a guide quotes Payload Cloud at $35 to $500 per month, that option is no longer open to new projects. Plan for self-hosting or another managed Node host instead.

To make the difference concrete, here is an illustrative three-year comparison for a mid-size content site. Every AEM input is an estimate from the ranges above, and the Payload side assumes a one-time rebuild plus modest hosting. Treat it as a model to populate with your own quotes, not a benchmark.

Cost line (3 years)AEM (Cloud Service)Payload (self-hosted)
Licensing~$600,000 (≈$200k/yr)$0 (MIT Licence)
Implementation / rebuild$500,000 to $1,000,000$150,000 to $400,000 (one-time)
Support contract~$90,000 to $150,000 (15 to 25% of licence)included in team cost
Hostingbundled in Cloud Service~$1,000 to $20,000 total
Indicative 3-year total~$1.2M to $1.75M~$150k to $420k

The numbers will move with your scale, integrations, and team rates. The shape holds across the migration write-ups: the large, recurring line is AEM licensing, and removing it is where the saving comes from. The engineering work does not disappear. It shifts to your team, which is a good trade for most organisations and a poor one for a few.

What the Figma acquisition changes for you

A reader evaluating Payload in 2026 already knows this happened, so here is what it means for a migration decision.

Figma announced its acquisition of the Payload team on June 17, 2025, confirmed on both the Figma blog and Payload’s own announcement. The facts that matter: Payload remains open-source under the MIT Licence, the GitHub repository is unchanged, and Figma has said it will keep investing in the project. The strategic logic is a tighter path from design in Figma to a real production backend.

The caution worth raising is about governance rather than code. A project backed first by venture capital and now by an acquirer moves fast, and it is governed differently from a foundation-run open-source project like Drupal. Payload Cloud closing to new sign-ups is one concrete, already-visible example of post-acquisition change. The team at Infonomic, who run Payload in production, make exactly this point and have hedged by starting an alternative project of their own, Byline CMS.

This is not a reason to avoid Payload. It does mean the platform’s direction is now Figma’s to set, and that belongs in your evaluation alongside the technical fit.

Planning the migration

A clean migration starts well before any code. Three phases of planning do most of the work.

First, a content audit. Inventory everything in your AEM instance: page templates and their components, Content Fragments and Experience Fragments, DAM assets, translations and language copies, and any custom workflows or business logic. You cannot model what you haven’t catalogued.

Second, content model mapping. AEM’s structures map onto Payload concepts once you see the pattern. AEM page components become Payload collections built from blocks. Content Fragments become collections. DAM assets move into Payload’s media library. Tags become relationship fields. The mapping itself is mostly mechanical. The friction shows up later, in localization and assets, which the section on what you have to rebuild covers.

Third, an integration assessment. List everything that touches an external system: Adobe Target, Adobe Analytics, third-party DAM connections, and any custom Sling servlets or APIs. This is where scope hides, and where migrations slip if you discover dependencies late.

The technical path

Your engineers will read this section closely. The end-to-end walkthrough with working code lives in the WegWorks guide, and the Payload side is documented in Payload’s own docs. The sequence looks like this.

Set up Payload and choose a database. Payload supports MongoDB and PostgreSQL. For an AEM migration, MongoDB’s document structure maps most naturally onto AEM’s JCR, which is also document-like. PostgreSQL is the right call if you need relational guarantees or already run Postgres.

Model your content. Recreate AEM pages as Payload collections with a blocks field, where each block represents a component type (hero, content, call to action). Model Content Fragments as their own collections with typed fields and relationships to authors and categories. Add a legacyId field to any collection you migrate into; you will use it for idempotent imports and for the redirect map later.

Export from AEM. Use AEM’s QueryBuilder or Content Services to pull pages and Content Fragments out as JSON. Export DAM assets as a package, or pull them through the asset download endpoint.

Migrate assets first. Walk your extracted DAM files and create a media document in Payload for each one. Store the original DAM path on a legacyPath field alongside alt text and metadata. Assets go in before content, so that when a page references an image by its DAM path, the matching media document already exists to link to.

Bring the content into Payload. There are two documented routes. The first is the Local API: write a script that reads your exported JSON, loops over each record, and calls payload.create() (or payload.update()) to insert it. The script runs Payload locally, so there is no HTTP layer in the way.

Two things this script gets right that naive versions miss. It keys on legacyId and upserts, so re-running it updates records instead of creating duplicates. And it passes context: { disableRevalidate: true }, which your hooks read to skip revalidation, indexing, and webhooks during a bulk load. In Payload 3, you get the running instance with getPayload, not payload.init().

import { getPayload } from 'payload'
import config from '@payload-config'
import aemPages from './aem-pages.json'

const payload = await getPayload({ config })

for (const page of aemPages.hits) {
  const content = page['jcr:content']
  const legacyId = page['jcr:path']

  const data = {
    legacyId,
    title: content['jcr:title'],
    slug: legacyId.replace('/content/mysite/', ''),
    // carry AEM's replication state instead of forcing everything to draft
    status:
      content['cq:lastReplicationAction'] === 'Activate' ? 'published' : 'draft',
    layout: await transformComponents(content), // see below
  }

  // upsert on legacyId so re-runs update rather than duplicate
  const existing = await payload.find({
    collection: 'pages',
    where: { legacyId: { equals: legacyId } },
    limit: 1,
  })

  if (existing.docs.length) {
    await payload.update({
      collection: 'pages',
      id: existing.docs[0].id,
      data,
      context: { disableRevalidate: true },
    })
  } else {
    await payload.create({
      collection: 'pages',
      data,
      context: { disableRevalidate: true },
    })
  }
}

On the hook side, guard your side effects so the flag actually does something: if (context.disableRevalidate) return at the top of the relevant afterChange hooks.

The second route is the Import/Export plugin, which handles CSV and JSON exports and supports importing through a dedicated imports collection. For schema changes over time, Payload’s migrations system gives you a versioned path with payload migrate:create and payload migrate.

Transform components. The real work is a transformation script that reads each AEM component’s sling:resourceType and maps it to the matching Payload block. Write one mapping per component type, log anything unmapped so nothing disappears silently, and filter the unmapped nulls out before they reach the layout array. Note that fileReference on an AEM component is a DAM path, not an asset ID, so you resolve it to the media document you imported in the assets step.

async function transformComponents(content) {
  const components = collectComponents(content) // walk jcr:content children
  const blocks = await Promise.all(components.map(mapComponentToBlock))
  return blocks.filter(Boolean) // drop unmapped (null) blocks
}

async function mapComponentToBlock(component) {
  switch (component['sling:resourceType']) {
    case 'mysite/components/hero':
      return {
        blockType: 'hero',
        heading: component.heading,
        // fileReference is a DAM path; resolve it to the migrated media doc id
        backgroundImage: await resolveMedia(component.fileReference),
      }
    case 'mysite/components/text':
      return { blockType: 'content', richText: component.text }
    default:
      console.warn(`Unmapped component: ${component['sling:resourceType']}`)
      return null
  }
}

// media must already exist; look it up by the DAM path stored on each media doc
async function resolveMedia(damPath) {
  if (!damPath) return null
  const { docs } = await payload.find({
    collection: 'media',
    where: { legacyPath: { equals: damPath } },
    limit: 1,
  })
  return docs[0]?.id ?? null
}

Map old URLs to new ones. Because you stored legacyId (the AEM jcr:path) on every migrated document, you can build the redirect map straight from the data: a 301 from each old path to its new slug for pages that moved, and a 410 for pages you dropped so search engines stop asking for them. Load the map into Payload’s redirects plugin or your Next.js config, and validate it on staging against a crawl of the live AEM sitemap before you cut over.

Rebuild the front end. With content in Payload, your Next.js app queries the Payload API. A page fetches the matching published document by slug, then renders each block in the layout array with its own component.

const { docs } = await payload.find({
  collection: 'pages',
  where: {
    slug: { equals: params.slug },
    status: { equals: 'published' },
  },
  limit: 1,
})

This pattern is not theoretical. The Infonomic team, who migrated a production site onto Payload, describe the same sequence from a real project: export through the source system’s JSON API, write import scripts against Payload, then rebuild the front end in Next.js. They came from Drupal rather than AEM, but the export-transform-import-rebuild sequence is the same whatever you’re leaving.

What you have to rebuild, and what nobody tells you

The migration guides are reliable on the mechanical steps. They say much less about the friction, so this section covers both.

The expected rebuilds are straightforward to name. Personalisation that ran on Adobe Target needs a replacement such as LaunchDarkly or Optimizely. Adobe Analytics can keep running through a client-side integration. You rebuild AEM workflows with Payload hooks or an external workflow engine. And the team needs time to get comfortable on the TypeScript and Node stack if they’re coming from Java.

The friction is where you should set expectations early.

AEM’s integration depth is well documented. Adobe’s own Experience League docs show that connecting Target and Analytics runs through an IMS configuration and Adobe Launch tag management, with A4T linking Target experiences to Analytics reporting. Leaving AEM means you rebuild that personalisation and measurement chain yourself. That is a project of its own, not a checkbox you switch off.

Localization changes shape, and it catches teams that skim past it. AEM handles translations through language copies and the Multi Site Manager, where each locale is its own page tree kept in sync with a blueprint. Payload localizes at the field level: a single document holds every locale, and you mark individual fields localized: true. The migration has to fold each AEM language copy onto the matching locale of one Payload document. Watch one trap during import. A blocks or array field marked localized stores a separate value per locale, so if your import script writes the array without setting the locale option, each write overwrites the previous locale instead of adding to it. Import one locale at a time, passing locale on every create and update call. If multi-language is out of scope for your first cut, decide that on purpose and skip it, rather than discovering the gap mid-build.

Moving off JCR loses data if you let it. A practitioner account of an AEM migration names the recurring failure points, and they apply regardless of which headless platform you land on: preserving metadata, links, and references when you flatten a hierarchical content tree into documents; carrying DAM assets across with their associations intact; mapping old URLs to new ones so SEO survives; and retraining authors on a new editing model. Audit and back up before you import, then validate after.

Payload moves work onto your front-end team. The Infonomic write-up is refreshingly blunt about this. Payload hands you responsibility for front-end serialization, the job of turning Payload’s blocks and fields into rendered pages with your own component library. Its media handling, at the time they wrote, was not yet a full replacement for a dedicated DAM. And it leans towards Next.js, which helps if that is your stack and adds a deployment decision if it isn’t. For a team coming from AEM’s built-in rendering and mature DAM, those are concrete costs to plan for at the start.

Timeline and outcomes

On timeline, FocusReactive puts a typical AEM-to-Payload migration at 8 to 12 weeks, depending on data structure, the number of integrations, and how much custom backend logic you need. Shorter figures circulate in search summaries, but 8 to 12 weeks is the number we’d plan against until your own audit says otherwise.

On outcomes, the most concrete published results come from FocusReactive’s work with Reverse Health. Treat these as that client’s numbers, not an industry average: 42 percent faster content publishing, roughly 2× faster page loads, 75 percent fewer manual workflows, and full ownership of content and infrastructure. Your own results will depend on where you start.

So, should you move?

If you run a content or marketing site, your costs are dominated by AEM licensing and DevOps, and you are not deeply tied to Adobe Target, Analytics, and Creative Cloud, Payload is a strong move. You trade licensing spend for engineering control and a modern stack your team can hire for.

If your marketing operation lives inside the Adobe stack and depends on that integration, the migration cost includes rebuilding it, and AEM may still be the right platform. We would tell you that before you started.

Keep one thing in mind throughout. In an AEM-to-Payload migration, most of the effort goes into rebuilding the admin experience, the front end, and the integrations. Moving the data across is the smaller part. Teams that plan around that tend to avoid the nasty surprises late in the project.

We’re a Payload partner agency and top contributor. If you want a second opinion on whether Payload fits your stack, book a call, and we’ll tell you if it’s the right move or if it isn’t.

FAQ

  1. How long does an AEM to Payload migration take?

    FocusReactive puts a typical migration at 8 to 12 weeks. The figure moves with how much content you hold, how many integrations you depend on, and how much custom backend logic you need. Most of that time goes into the rebuild, not the data transfer.

  2. How much does Payload cost compared to AEM?

    Payload is free to license under the MIT Licence, so your spend is hosting and engineering. Self-hosting runs from roughly $10 per month on a small VPS to a few hundred per month on a cloud provider. AEM has no public list price, but industry estimates put Cloud Service licensing in the low hundreds of thousands per year before implementation and support. The saving is mostly the licence you stop paying.

  3. Will we lose content or SEO when we migrate?

    You don't have to, but it takes deliberate work. The risk is real when you flatten AEM's hierarchical content into documents. Map old URLs to new ones, set up redirects, and carry across metadata and asset associations, then validate everything after import. Audit and back up before you start.

  4. Is Payload secure enough for enterprise use?

    Payload is self-hosted, so it runs inside your own infrastructure and can connect to your identity provider, encryption, and security policies. That puts data residency and access control in your hands rather than a vendor's.

  5. What changed when Figma acquired Payload?

    Figma announced its acquisition of the Payload team in June 2025. Payload stays open-source under the MIT Licence and the GitHub repository is unchanged. The visible change for new projects is that Payload Cloud, the managed-hosting option, has paused new sign-ups, so plan to self-host or use another Node host.

  6. MongoDB or PostgreSQL?

    Both are supported. MongoDB's document model maps most naturally onto AEM's JCR, which makes the import simpler. Choose PostgreSQL if you need relational guarantees or already run Postgres in production.

  7. Do we have to use Next.js?

    Payload integrates tightly with Next.js, and that pairing lets you run the admin panel, the API, and the front end in one application. If you use a different framework, you can still consume Payload's REST or GraphQL API and deploy the admin separately.

Sources


Author

Paul Utr

Co-founder, Chief Growth Officer

Paul has been launching online platforms since his teens, picking up UX and product design by building them. He led the Mailgun redesign at Netguru and was Principal Designer at Ramp Network through its seed-to-Series-B run. At WAYF he leads design and organisational alignment, and watches how language carries through every product we ship.


Rather have it done for you? WAYF runs Adobe Experience Manager to Payload migrations end to end.

We're booking content platform
engagements for 2026.

Twenty-five minutes to walk through the work and decide if we're the right team for it. Scoping and a fixed price come after.