keelapps/Satchel for Confluence/Documentation
Documentation
Satchel indexes a whole Confluence space so you can see, download, move and clean up every file at once — and move many attachments to another page at once, where Confluence moves them one at a time. Where the platform makes a thing uncertain, Satchel reports the uncertainty rather than rounding it off.
Where Satchel lives
- In any space: the sidebar → Space apps → Satchel — the browser: every file in the space, with search, filters, selection across pages, and the bulk actions. Anyone who can view the space sees it there.
- On a page: ••• (More actions) → Apps → Manage attachments — one page, read live rather than from the index.
- Confluence settings (the gear) → Apps → Satchel — cross-space totals, uploader rankings, the audit trail, and the settings, including the bulk-delete switch. Site administrators only.
The browser is not under Space settings. The Content → Attachments entry there is Confluence's own; Satchel is the entry under Space apps in the space's sidebar. If the sidebar is collapsed, expand it with the button at the top left.
The space attachment browser
A scan builds the space's inventory once — for every attachment: id, file name, size, media type, version count, upload timestamp, uploader and status, plus the id, title and status of the page it is on. Metadata only; file content is never stored.
The scan is checkpointed and resumable, and an hourly job resumes a scan paused for the API budget, so a space with tens of thousands of files finishes without anyone babysitting it. From the resulting table you can search by name, filter by page, media type, uploader and size, select across page boundaries, and then download, move or delete.
Aggregate totals shown on a space page — file count, total size — cover the whole space and are labelled as such, next to a count of how many rows are hidden from you.
Moving attachments between pages
Confluence Cloud moves an attachment from its Properties, one file at a time. Moving many at once is CONFCLOUD-15025, open since March 2009; in 2022 Atlassian said it has no plans to prioritize it. Data Center's purpose-built move endpoint does not exist on Cloud.
Pick the files, pick the target page, and read the dry run: what will move, what will not and why, and which route the move will take on your site. There are two, and they are not the same operation:
| Route | What happens | Consequences |
|---|---|---|
| Re-parent | a single call changes the attachment's parent page | the attachment id, the file id and the whole version history survive; no bytes are transferred |
| Copy then delete | the file is read, uploaded to the target page, and the original is trashed | a new attachment id and file id, version history restarting at 1, and any page body that embedded the file by its old id no longer resolves |
Satchel attempts the re-parent first and learns from the answer. Only the endpoint saying the operation does not exist downgrades your site to the copy route; a permission error or a missing file fails that one row instead, because reading “this file is gone” as “this Confluence cannot move files” would silently push every later move onto the id-destroying route. What it learns is cached per site, and the record only ever downgrades.
Every moved file is reported with the route that actually ran. Nothing in the interface claims ids were preserved unless a re-parent really succeeded.
On the copy route, the original is deleted only after the copy has been observed on the target page. There is no code path in which the original is gone and the copy was never made.
Zip download
Select files anywhere in the space and download one archive, foldered by page title, with a CSV manifest inside listing what it contains. Before it starts, Satchel says how many files and how many megabytes it will fetch, and roughly what that costs in Confluence API points.
One archive holds up to 2,000 files or 2 GB. Files past either limit are listed in the manifest instead of being packed, so you can select them and download a second zip. A file the archive could not include is listed too, with the reason in words.
The archive is assembled in your browser tab. The compression library is part of the app's own bundle rather than fetched from a content delivery network — a CDN would require an external network permission, and that permission is precisely what this app does not have. Bytes go Confluence → your tab → your disk, with nothing stored in between.
There are two routes for getting the bytes into the tab, and Satchel measures which one returns them intact on your site rather than assuming: one calls Confluence directly from the browser, the other relays each file through the backend, up to 4 MiB per file. The direct route is faster; the relay is correct for every file type. Both are zero-egress, and the app says which is in use.
Orphans
Four buckets, and they are not equally certain:
| Bucket | Confidence |
|---|---|
| Files on deleted pages | Certain |
| Files on archived pages | Certain |
| Files in the trash, kept until the trash is purged | Certain |
| Files their own page does not embed or link | Inferred |
The last one is labelled inferred because that is what it is. The check reads the parent page's body for a reference to the file; a file may still be linked from another page, from a comment, or from somewhere outside Confluence entirely. The interface also distinguishes “checked and found nothing” from “never looked” — a page whose body could not be read is not evidence that its files are unused.
Page bodies are read during a scan to answer this question and are discarded immediately; only a true-or-false flag per attachment is kept.
Duplicate candidates
Files are grouped by normalised filename and identical byte count, with the space each group would reclaim.
They are candidates, and every surface says so, because there is no content hash: the file id identifies the stored file for that one attachment, not a checksum shared across identical uploads. Proving two files byte-identical would mean downloading both.
Two exclusions follow from the same reasoning:
- Same name, different size is not grouped. Those are two revisions, and deleting one loses work.
- Same size, different name is not grouped. At small sizes that is coincidence — two 31-byte spreadsheets with different names are two spreadsheets.
Storage, and the number it will not invent
Storage is reported by page, by uploader and by media type, plus the largest files. Any of it exports as CSV.
One figure is deliberately absent. Confluence reports the current version's size and the version count, but not the size of prior versions and no aggregate over them. Multiplying size by count would be a fabrication — a document whose first draft was 2 KB and whose tenth is 8 MB is not 80 MB. The storage tab therefore reports version counts and states what it cannot tell you, rather than printing a confident wrong number.
In the same spirit, “this space has no attachments” is never asserted as a fact. Confluence can answer a read it could not serve with an empty success, so a scan can succeed, index nothing, and be entirely wrong. Satchel records that case, phrases it as a question, and gives administrators a one-call check that settles it.
Bulk delete and the trash
- Bulk delete is off by default and must be enabled by a site administrator.
- It requires a dry run and a typed confirmation.
- Deletions go to Confluence's own trash, where a space administrator can restore them through Confluence itself.
- Every deletion is recorded: timestamp, action, who did it, the file name and size, and the pages involved.
Satchel never edits page content. Its only writes are attachment moves and deletions, both explicitly initiated, both behind a dry run and a confirmation, and both recorded.
The audit trail is a bounded ring of the 2,000 most recent events; older events age out. Export it as CSV if you need a longer record — the button is in Confluence administration and on each space page. A rescan deliberately does not rewrite it: an audit record that can be edited is not an audit record.
Undo
A completed move records an undo journal, so it can be reversed. Journals expire 30 days after the move they describe.
API points and pacing
Confluence charges apps for API calls in points — a base point per call plus one per object returned — and the hourly allowance belongs to the app, shared across every tenant running it rather than granted per site. One customer scanning a large space could rate-limit every other customer.
So Satchel caps itself well below that ceiling per site, paces its requests, shows every estimate before spending it, and defers to the next hour rather than pushing through. A scan that says it is waiting for the next window is working as designed; the hourly resume job will pick it up.
Who can see what
- The space attachment browser is visible to anyone who can open the space, but by default it hides rows whose page that person cannot open — Satchel intersects its inventory with the pages Confluence says you may read before any row leaves the server. A site administrator can switch this off.
- Cross-space totals, the full audit trail and the site settings live in Confluence administration, for site administrators. In a space, members see the files, reports and activity for pages they can open; space administrators see all of the space's activity and change its settings.
- Every download, move and delete is performed as the signed-in user, so Confluence's own permissions decide what actually happens regardless of what the index shows.
Permissions the app requests
| Scope | Why |
|---|---|
read:page:confluence | page id, title and status, and the page body read during a scan to answer the “is anything referencing this file” question |
read:space:confluence | enumerate spaces and resolve the one you are in |
read:attachment:confluence | attachment metadata, and the bytes when you download |
write:attachment:confluence | the move — re-parenting an attachment, or uploading the copy when the fallback route runs |
read:content-details:confluence | the details a move and a listing need beyond the basic page record |
delete:attachment:confluence | bulk delete, which ships switched off and sends files to the trash |
read:user:confluence | resolve uploader account ids to display names |
storage:app | the inventory, job records, undo journals, the audit trail and settings, in Atlassian-hosted storage on your own site |
There is no permissions.external block: the app cannot make an outbound call
even if it tried to, which Atlassian enforces at the platform level. There is no scope for
editing page content, and no path to it in the interface.
Not in this release
- Uploading new attachments. Satchel moves and removes; it does not add.
- Editing page content. Deliberately, and permanently.
- Content-hash duplicate detection. Not possible; see duplicate candidates.
- Version-history storage totals. Not computable from what Confluence reports.
- Scheduled scans. Scans run on demand, and resume automatically if interrupted.
- Jira attachments — those are Satchel for Jira.
Support
Questions, bug reports and feature requests: raise a support request. Security issues: report privately.