Seven Node.js tools that repair Confluence Cloud content after a Data Center migration — the macros, page structure and permissions the migration assistant does not carry across intact.
A Confluence migration moves pages. It does not guarantee that what is inside the pages still works — and none of the four failures below appear in the migration report.
The macro name in storage format changes, or the Cloud build of the app expects different parameters. The page renders an "Unknown macro" placeholder where the content used to be, and the migration reports success.
Nested bodied macros are legal on Data Center and unsupported by Cloud's Fabric editor. The page renders wrong and the editor refuses to open it at all.
Anything storing a DC username or group name stops matching, because Cloud is keyed on accountId and groupId. Visibility macros fail open — content meant for one group becomes visible to everyone, silently.
One tool per failure mode. Every one of them is a storage-format rewrite: discover the affected pages, parse the XHTML, splice in the change, back up the original, PUT it back. Nothing is regenerated; everything else on the page stays byte-identical.
Makes your API-token account a space administrator on every space, so the PUTs are actually allowed. Site admin is not enough — run this first or the other six spend a long run collecting 403s.
Extracts the raw HTML from the Data Center storage format and replaces the broken macro block on Cloud with the original content. Plan-driven, resumable, retryable.
Rewrites nested bodied macros so the inner macro becomes a sibling, using a split-around-child strategy that handles arbitrary depth and multiple siblings. Containers that cannot be split safely are reported, not mangled.
Reads ground truth from the DC page, resolves DC usernames and group names to Cloud accountId and groupId, and rewrites the macro parameters so the condition matches real people again.
Rewrites Deck-of-Cards and Card macros whose names collide with Cloud natives to the Cloud-compatible legacy equivalents. Default-deny: an ambiguous card with no signal is skipped with a recorded reason.
The original macro XML survives in page storage even when the app does not exist on Cloud. Discovers it by CQL, resolves the named users to Cloud identities, and PUTs an equivalent Cloud macro in its place.
Backs up and strips DC page restrictions so a blocked app migration can run, then restores them on Cloud afterwards — translating usernames to accountId and reporting every one it could not resolve.
The same operating model throughout, deliberately — so that knowing one tool means knowing all of them.
A read-only plan phase discovers affected pages and writes a reviewable JSON plan. An execute phase acts on it. --dry-run runs the second phase without the PUT. You are expected to read the plan before you run it.
Each modified page gets its original storage XHTML, a unified diff and a metadata JSON written under backups/ before the new version is sent. Recovery is per page, not all-or-nothing.
Where a page could legitimately be either shape, the tool skips it and records the reason rather than guessing. A skipped page you can fix by hand is cheaper than a corrupted page nobody notices.
Re-running is safe — already-converted pages are detected and skipped — and an interrupted run resumes from its plan file rather than starting over.
git clone https://github.com/leanzero-srl/leanzero-confluence-migration-toolkit.git cd leanzero-confluence-migration-toolkit/grant-space-admin npm install cp .env.example .env # CLOUD_BASE_URL (with /wiki), CLOUD_EMAIL, CLOUD_API_TOKEN node main/grant_space_admin.js --dry-run node main/grant_space_admin.js --skip-personal
Every question this repository raises, answered in order: what to set up before you touch a page, how to repair each class of broken macro, how to fix page structure and identity, and how to roll back and prove a run actually worked. 23 sections.
Node 18 or newer, a Confluence Cloud API token, and — the part everyone skips — space-admin rights on every space you intend to edit. Site admin is not enough.
Every tool in this repository is a plain Node.js CLI. There is nothing to install into the tenant, no app to approve, no Forge deploy. Each one talks to the public Confluence REST API over Node's built-in https using Basic auth (email:api_token), and writes its output to local files you can read.
| What | Version | Why |
|---|---|---|
| Node.js | 18 or newer | The tools use modern syntax and the built-in https client. No transpile step. |
| npm | ships with Node | Installs dotenv, and fast-xml-parser where storage XHTML has to be parsed. |
| A Confluence Cloud API token | any | Created at id.atlassian.com → Security → API tokens. Used as Basic auth with your account email. |
| Space admin on the target spaces | — | The blocker nobody expects. See the next section. |
| A Data Center account | only for DC→Cloud tools | html-macro, visibility-macro and page-restrictions read ground truth from DC. |
403 on PUT /wiki/api/v2/pages/{id}. This is the single most common reason a first run produces a long list of failures that look like a credentials problem and are not.Run grant-space-admin once. It adds your token's own account as an administrator on every space, additively and idempotently.
git clone https://github.com/leanzero-srl/leanzero-confluence-migration-toolkit.git
cd leanzero-confluence-migration-toolkit/grant-space-admin
npm install
cp .env.example .env # CLOUD_BASE_URL (include /wiki), CLOUD_EMAIL, CLOUD_API_TOKEN
node main/grant_space_admin.js --dry-run # see what would be granted
node main/grant_space_admin.js --skip-personal # grant, excluding ~personal spacesDELETE /wiki/rest/api/space/{key}/permission/{id} using the ids in logs/grant_<epoch>.log.Discovery dump, then one page, then one space, then the site — opening a browser at every step. A storage-format rewrite that is syntactically valid and semantically wrong reports success and renders an empty box.
--discovery-dump (or equivalent) that prints the untouched storage format of a handful of matches and exits. Storage format varies by app version — confirm the exact ac:name and parameter shape on your tenant before assuming the tool's defaults fit.logs/. Open it. The plan is the contract: if it lists a page you did not expect, stop.--dry-run --space TEST --limit 1. This produces the backup, the unified diff and the metadata JSON without sending the PUT. Read the diff.PUT, the modified page's original storage XHTML, a unified diff and a metadata JSON are written under backups/. That directory is your undo, and it is per page — you can restore one page without touching the rest.Stated up front, because discovering a limit mid-cutover is the expensive way to find it.
The toolkit is free and Apache-2.0. If you would rather have someone who has already hit every one of these edge cases run it with you, that is what we do.