keelapps

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

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:

RouteWhat happensConsequences
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:

BucketConfidence
Files on deleted pagesCertain
Files on archived pagesCertain
Files in the trash, still counting against your storageCertain
Files no page body referencesInferred

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:

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

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

Permissions the app requests

ScopeWhy
read:page:confluencepage id, title and status, and the page body read during a scan to answer the “is anything referencing this file” question
read:space:confluenceenumerate spaces and resolve the one you are in
read:attachment:confluenceattachment metadata, and the bytes when you download
write:attachment:confluencethe move — re-parenting an attachment, or uploading the copy when the fallback route runs
read:content-details:confluencethe details a move and a listing need beyond the basic page record
delete:attachment:confluencebulk delete, which ships switched off and sends files to the trash
read:user:confluenceresolve uploader account ids to display names
storage:appthe 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

Support

Questions, bug reports and feature requests: raise a support request. Security issues: report privately.