How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Add, reorder or remove a section on many pages

Adding a section is safe with an append or an insert, one patch per page. Reordering and removing are not, once a page's positions have drifted: read the collection, change the list in your code, and write it back whole with areaContent, which also renumbers it.

Context

Context. Every page on one layout needs a new section at the end, or a retired component taken out, or two sections swapped. In the admin that is an afternoon of clicking; through the API it is a loop. One rule keeps it safe: a collection's positions are not renumbered after a removal, so name a position against the collection as it stands right now, not as it stood earlier (Positions drift). Adding at the end or inserting is unaffected; anything that names an existing element's position is not.

Before you start

Credentials for a backend user who may edit pages, the layout's id and the collection area's name (pageSections here), and the component's layout id and areas for anything you add. Try the loop on one page of a development site first.

Steps

  1. Find the pages: GET /cdna-api/pages/pages?activeRevision.layout.id=30&properties={id,activeRevision{id}}&limit=100, then the next page of results until none are left. A page keeps its layout on the active revision, not on the record itself, so filter on activeRevision.layout.id, not layout.id.
  2. To add a section, send each page a patch with originRevision, "activeRevision": true and appendAreaCollectionElement(pageSections), or insertAreaCollectionElement(pageSections, n) for another place.
  3. To reorder or remove, read each page's areasContent, turn the collection into the write shape, reorder or drop elements in your code, and send the whole list as areaContent(pageSections) in the patch. The rewrite stores the elements at 1, 2, 3 again, so the page is safe for positional edits afterwards.
  4. Log each page's id and new revision id. A page that came out wrong is then one rollback away (Roll back a page).
  5. Check a few pages signed out, and stop the loop at the first answer that is not a 200.

Code

HTTP Adding: a real request and answer on this site's sandbox page
PUT /cdna-api/pages/page/1885?properties={id,activeRevision{id}}
Content-Type: application/json

{
  "patchRevision": {
    "originRevision": 3447,
    "activeRevision": true,
    "appendAreaCollectionElement(pageSections)": {
      "layout": 58,
      "name": "Sandbox figure",
      "areas": {"image": 1185, "alt": "A blue and green gradient", "caption": "Uploaded through the API"}
    }
  }
}

# 200 {"result":"success","data":{"id":1885,"activeRevision":{"id":3482}},"message":"The Page has been saved successfully."}

Reordering and removing, as a script: it reads each page, applies your change to a plain list, and writes the list back. The mapping from the read shape to the write shape is the part to get right; this one handles text, switch, image, file, record and component areas, and keeps global components as references.

JavaScript
// rewrite-sections.js (Node 18 or later); coredna() is your client module, as in
// https://hub.coredna.com/books/apis/client/client-layer
import { coredna } from './coredna.js';

function toWrite(area) {                     // one area of areasContent -> the value a write takes
  if (!area || typeof area !== 'object') return area ?? null;
  switch (area.category) {
    case 'collection': return (area.collection ?? []).map(componentToWrite);
    case 'component':  return area.component ? componentToWrite(area.component) : null;
    case 'image':      return area.imageId ?? null;
    case 'file':       return area.fileId ?? null;
    case 'record':     return area.record?.id ?? null;
    case 'records':    return (area.records ?? []).map((r) => r.id);
    default:
      if ('active' in area) return area.active;   // a switch
      return area.text ?? null;                    // text, rich text, select, date; a multiselect's JSON string
  }
}

function componentToWrite(c) {
  if (c.componentGlobal) return { global: true, id: c.componentId };   // keep the reference, not a copy
  const areas = {};
  for (const [name, area] of Object.entries(c)) {
    if (name.startsWith('component')) continue;
    const value = toWrite(area);
    if (value !== null) areas[name] = value;
  }
  return { name: c.componentName, layout: c.componentLayout, areas };
}

// change(list) returns the new list: reordered, filtered, or both
export async function rewriteSections(pageId, change) {
  const { data } = await coredna('GET', 'pages/page/' + pageId, {
    query: { properties: '{id,activeRevision{id,areasContent}}' },
  });
  const live = data.activeRevision;
  const before = toWrite(live.areasContent.pageSections) ?? [];
  const after = change(before);
  const result = await coredna('PUT', 'pages/page/' + pageId, {
    query: { properties: '{id,activeRevision{id}}' },
    body: { patchRevision: { originRevision: live.id, activeRevision: true, 'areaContent(pageSections)': after } },
  });
  console.log(pageId, live.id, '->', result.data.activeRevision.id, before.length, '->', after.length);
}

// remove every section built on a retired component layout:
// await rewriteSections(1885, (list) => list.filter((s) => s.layout !== 41));
// move the last section to the top:
// await rewriteSections(1885, (list) => [list.at(-1), ...list.slice(0, -1)]);

Why this and not that

Not removeAreaCollectionElement in a loop over pages whose history you do not know: on a page where an editor or an earlier script removed a section, the position you send can name the next one, and the request still answers 200. Not several separate requests per page: within one request every position refers to the collection before it, so all of a page's changes belong in one patch, or in one rewrite. Two things a rewrite does not carry over, because the read does not show them: an element saved hidden comes back visible, since an element is published unless the write says "published": false; and areas of input types the mapping does not handle (files, records) come back empty. Extend toWrite after reading one such area, and check a hidden element before you rewrite its page.

Go deeper

Was this page helpful?

Your answer goes to the docs team.