Generate category pages programmatically
When you need more category pages than are practical to create with the manual tutorial, but the automated AEM Commerce Prerender solution doesn’t fit your catalog’s scale or requirements, you can generate the pages with a script. You produce the same category document — a product-list-page block plus a metadata block — but instead of authoring it in Document Authoring, you build it from a template, push it to Document Authoring through its Source API, and publish the path through the Edge Delivery Services pipeline.
This is a developer workflow: you write and run the script, and you own when it runs again.
Prerequisites
Section titled “Prerequisites”Before starting this tutorial, make sure you have:
- A working Commerce storefront on Edge Delivery Services
- Comfort writing and running a JavaScript (Node.js) script
- Access to your Commerce catalog’s GraphQL endpoint — Catalog Service (Adobe Commerce as a Cloud Service or PaaS) or the Merchandising Services API (Adobe Commerce Optimizer)
- An Adobe IMS bearer token for the Document Authoring Source API and one for the AEM Admin API (see Step 4 for how to generate these)
How it works
Section titled “How it works”Your script fetches category data from Adobe Commerce, builds a Document Authoring document from a template containing the product-list-page and metadata blocks, then publishes it through the Document Authoring and Edge Delivery Services APIs — the same preview/publish pipeline the rest of your site uses. The tasks below walk through each part in detail.
Re-run the script whenever the category or its metadata changes. The output is a normal Document Authoring document, so an author can still open and edit it afterward.
Step 1: Fetch category data from Commerce
Section titled “Step 1: Fetch category data from Commerce”Query your Commerce backend for the category and the products you want to list. Use your own query — YOUR_CATALOG_QUERY below stands in for it.
// Adobe Commerce as a Cloud Service / PaaS + Catalog Service: post to your Catalog Service GraphQL endpoint.// Adobe Commerce Optimizer: post to your Merchandising GraphQL endpoint with your Optimizer headers.const response = await fetch(CATALOG_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', // ...your store/context headers... }, body: JSON.stringify({ query: YOUR_CATALOG_QUERY, variables: { categoryPath }, }),});
const { data } = await response.json();For ready-made category queries — fetching categories, the category tree, and product listing data by category path — see the queries in the aem-commerce-prerender repository (actions/queries.js on main), such as CategoriesQuery, CategoryTreeQuery, CategoryTreeBySlugsQuery, and PlpProductSearchQuery. For the schema each backend exposes, see the Catalog Service GraphQL schema (Adobe Commerce as a Cloud Service and PaaS) or the Merchandising Services API guide (Adobe Commerce Optimizer).
Step 2: Build the document from a template
Section titled “Step 2: Build the document from a template”Render the category data into a Document Authoring source document. A DA source document is HTML with a specific shape: a <body> holding <header>, <main>, and <footer>, where <main> contains one or more sections (each a <div>). A block is a <div> whose class is the block name, with one <div> per row and one <div> per cell. This markup is the same as what Edge Delivery Services serves, so you can confirm the exact structure for any page by appending .plain.html to its URL.
Produce the same two blocks the manual tutorial describes — a product-list-page block bound to the category through urlPath, and a metadata block for SEO.
<body> <header></header> <main> <div> <div class="product-list-page"> <div> <div>urlPath</div> <div>apparel</div> </div> </div> <div class="metadata"> <div> <div>title</div> <div>Apparel</div> </div> <div> <div>description</div> <div>Shop the apparel collection.</div> </div> <div> <div>json-ld</div> <div>{ "@context": "https://schema.org", ... }</div> </div> </div> </div> </main> <footer></footer></body>Fill the values from the data you fetched in Step 1 — the category’s urlPath, title, description, and structured data. For the full list of metadata properties, see Page metadata. Generate one canonical document per category.
Step 3: Create the document in Document Authoring
Section titled “Step 3: Create the document in Document Authoring”Post the document to the Document Authoring Source API . The path you post to becomes the document location in your content tree and the page URL — a document at /foo/apparel.html is served at /foo/apparel.
const form = new FormData();form.append('data', new Blob([documentHtml], { type: 'text/html' }));
// path must include the file extension, e.g. apparel.htmlawait fetch(`https://admin.da.live/source/${org}/${repo}/${path}.html`, { method: 'POST', headers: { Authorization: `Bearer ${DA_TOKEN}` }, body: form,});Step 4: Publish the path through Edge Delivery Services
Section titled “Step 4: Publish the path through Edge Delivery Services”Creating the source document in DA doesn’t make it live. Publish it the same way the rest of your site publishes: call the AEM Admin API to preview the path first, then publish it.
const base = `https://admin.hlx.page`;const opts = { method: 'POST', // The AEM Admin API expects the token in both headers. headers: { Authorization: `Bearer ${AEM_TOKEN}`, 'x-content-source-authorization': `Bearer ${AEM_TOKEN}`, },};
// Preview, then publish. ref is your branch, typically "main".await fetch(`${base}/preview/${org}/${site}/${ref}/${path}`, opts);await fetch(`${base}/live/${org}/${site}/${ref}/${path}`, opts);The preview call pulls your new document into the preview environment, and the live call copies it to production and purges the CDN. After the live call succeeds, the category page is served at its path.
Step 5: Refresh when the catalog changes
Section titled “Step 5: Refresh when the catalog changes”Because there’s no change detection, re-run the script to refresh a page after the category or its metadata changes. Run it on demand, or schedule it (for example, a cron job or a scheduled App Builder action) at whatever cadence your catalog needs. Re-posting to the same path overwrites the existing document, so manually authored changes do not persist. If authors change the pages manually, add a read/merge mechanism to this flow.
Change a category page’s URL
Section titled “Change a category page’s URL”When you change the path your script generates for a category — for example, from /apparel to /clothing — publishing the new path is only part of the process. The old path keeps serving the old document until you remove it, and once removed it returns a 404 unless you redirect it. This error is the easiest one to make when you regenerate pages at new paths: the old URL gets orphaned, and you lose the inbound links and search equity pointing at it.
Whenever your generation logic changes a category’s output path:
- Generate and publish the document at the new path, following Steps 2–4.
- Remove the old page: delete the old document from Document Authoring and unpublish its path through the AEM Admin API, so it’s no longer served at the old URL.
- Add a 301 redirect from the old path to the new one — for example,
/apparel→/clothing.
You can automate the redirect in the same script: the redirects sheet is itself a Document Authoring resource, so append the old-to-new row and republish it. See Redirects and Redirects on AEM.live for the sheet format, then update your sitemap.
Which approach should you use?
Section titled “Which approach should you use?”| Approach | Best for | Change handling |
|---|---|---|
| Prerender | Most catalogs; hands-off category and product pages | Automatic, on a fixed cadence |
| Manually authored page | One or a few categories, or a page that needs custom content | You edit the document |
| Generate programmatically (this tutorial) | Many category pages, or requirements the prerender limits block | You re-run the script |