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 it does the one thing Confluence itself will not, which is move an attachment to a different page. Where the platform makes a thing uncertain, Satchel reports the uncertainty rather than rounding it off.
Where Satchel lives
- Space → Attachments — the browser: every file in the space, with search, filters, selection across pages, and the bulk actions.
- A page's action menu → Manage attachments — one page, read live rather than from the index.
- Confluence administration → Satchel — cross-space totals, uploader rankings, the audit trail, and the settings, including the bulk-delete switch.
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 picks up any run that stopped part way, 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
This is the feature Confluence Cloud does not have. CONFCLOUD-15025 has been open since March 2009 and Atlassian has said it is not on the roadmap; 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.
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 the bytes through the backend in slices. 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, still counting against your storage | Certain |
| Files no page body references | 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, uploader rankings and the audit trail live in Confluence administration, so only site administrators reach them.
- 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.