Hello everyone,
My case is that I need to download the whole space with the same hierarchy of the tree, and with that I need to download a specific attachments from every page, is that possible with native ways or do I need scripts or plug-ins or an app from the marketplace .
I've tried to export the space in xml and html formatting and run a python script (https://community.atlassian.com/forums/Confluence-questions/How-can-a-Confluence-User-Download-all-Attachments-in-a-Space/qaq-p/1250380) to get the folders and attachments real title instead of the ID, and there's Two problem here :
1- I didn't get the same hierarchy of the tree as folders in my Pc (There's so much nested folders in the confluence tree).
2- All the attachments were exported and That's not what I need.
Given this information , is there a possible solution for this issue?
Any help would be greatly appreciated.
Thanks,
George
It's doable natively — the space export is what you're after. Next to the space name, More actions (•••) → Space settings → General → Export space, then choose HTML. You get a zip of the whole space with the page tree intact, and each page's attachments come down in their own folder named by the page ID, so both the hierarchy and the files are preserved. You'll need to be a space admin to run it.
The "specific attachments" part is where native stops, though — the HTML export pulls every attachment on each page, with no way to pick a subset. So you'd export the lot and cherry-pick from the per-page folders, or pull the specific files through the REST API if you want it automated. HTML export also skips blog posts and page comments, which is a known gap. And if the real goal is to move the space into another Confluence, use the XML export instead — that's the re-import format.
On a large space, if it errors or stalls, export it in smaller batches of pages.
I tested this, exporting with HTML to see if it keeps my hierarchy and I think the answer is both yes and no.
It dumps all attachments into one folder, all styles into one folder (was just a CSS Source File here), and then all pages into the main folder that these other two folders are in with no hierarchy to any of this.
When you open an HTML file, at the top it does show a hierarchy, it shows 1. Space name, 2. Space Home page, 3. Folder name, and then it displays the full page under that with attachments showing at the bottom.
I think the issue is that @George JH expected the export to preserve the same hierarchy in the exported folder, not in each HTML file, if that makes sense.
@George JH let us know your thoughts.
Have a great weekend!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Jason's testing is the accurate picture, and I owe you both a correction — the HTML export doesn't rebuild the tree as folders. The page files come out flat in one folder, and the hierarchy only survives as the links inside the HTML (the index.html ties them together in order). Attachments do get split by page, under download/attachments/<pageID>/, but that all sits under the one attachments folder, which is why it reads as flat. So you get a browsable offline copy; it doesn't mirror your tree on disk.
Since what you're after is the tree as folders plus specific attachments, the native export won't get you there — that's a script job. The v2 REST API has the pieces: list the space's pages (each carries a parentId, so you can rebuild the hierarchy), pull each page's attachments, and write it all into folders that match the tree.
// Export a Confluence Cloud space to a real FOLDER TREE on disk, each page with its attachments,
// using the v2 REST API. (Native HTML export flattens the tree — this mirrors it.)
// Node 18+. Env: CONF_BASE=https://you.atlassian.net CONF_EMAIL CONF_TOKEN SPACE_KEY
const fs = require("fs"), path = require("path");
const { CONF_BASE, CONF_EMAIL, CONF_TOKEN, SPACE_KEY } = process.env;
const auth = "Basic " + Buffer.from(`${CONF_EMAIL}:${CONF_TOKEN}`).toString("base64");
const H = { Authorization: auth, Accept: "application/json" };
const api = (p) => `${CONF_BASE}/wiki/api/v2${p}`;
const safe = (s) => s.replace(/[\/\\:*?"<>|]/g, "_");
async function getJson(url) {
const r = await fetch(url, { headers: H });
if (!r.ok) throw new Error(`${r.status} on ${url}`);
return r.json();
}
// Page through a v2 collection (follows the _links.next cursor).
async function all(pathAndQuery) {
const out = []; let url = api(pathAndQuery);
while (url) {
const j = await getJson(url);
out.push(...(j.results || []));
const next = j._links && j._links.next;
url = next ? CONF_BASE + "/wiki" + next.replace(/^\/wiki/, "") : null;
}
return out;
}
(async () => {
const space = (await getJson(api(`/spaces?keys=${SPACE_KEY}`))).results[0];
if (!space) throw new Error("space not found: " + SPACE_KEY);
const pages = await all(`/spaces/${space.id}/pages?limit=250`);
const byId = new Map(pages.map((p) => [p.id, p]));
// Build each page's folder path by walking its parentId chain up the tree.
const dirFor = (p) => {
const parts = []; let cur = p;
while (cur) { parts.unshift(safe(cur.title)); cur = cur.parentId ? byId.get(cur.parentId) : null; }
return path.join("export", ...parts);
};
for (const p of pages) {
const dir = dirFor(p);
fs.mkdirSync(dir, { recursive: true });
const full = await getJson(api(`/pages/${p.id}?body-format=storage`));
fs.writeFileSync(path.join(dir, "page.html"), (full.body?.storage?.value) || "");
const atts = await all(`/pages/${p.id}/attachments?limit=250`);
for (const a of atts) {
// Grab only the attachments you want: filter on a.title / a.mediaType here.
const dl = a.downloadLink || a._links?.download; // relative to /wiki
if (!dl) continue;
const r = await fetch(CONF_BASE + "/wiki" + dl.replace(/^\/wiki/, ""), { headers: { Authorization: auth } });
if (r.ok) fs.writeFileSync(path.join(dir, safe(a.title)), Buffer.from(await r.arrayBuffer()));
}
console.log(dir);
}
console.log(`Done: ${pages.length} pages.`);
})().catch((e) => { console.error(e.message); process.exit(1); });
It walks every page, builds the folder path from the parent chain, saves the page, and downloads its attachments into that folder — so you get the real tree on disk. Drop a filter on the attachments loop (a.title) to grab only the files you want per page.
Honest caveats: it uses a Confluence API token (email + token, Basic auth), you pass the space KEY, and it saves the page's storage-format HTML (the raw markup; if you need the rendered look that's a different endpoint). Run it against one small space first, and confirm the attachment download-link field on your instance.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@George JH if you are ok bulk downloading attachments alone use our app Attachment Manager. It works across an entire space. Possibly interesting are the filters, so in your case you can download only what you need or download the files in 'blocks'.
Free trial.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Pretty sure that's not possible in Confluence, at least natively.
But will see if anyone else responds that knows a way.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.