Storing PDFs and images in the same place without a bucket policy

30 August 2026

An app that stores anything usually ends up storing two kinds of thing: the pictures everybody should see, and the documents almost nobody should. Product photos and invoices. Screenshots and contracts. Avatars and passports.

They want opposite rules. The photos want to be public, cached forever and dropped straight into an <img>. The documents want to be unreadable without credentials, uncached, and refused entirely if someone uploads a .exe.

In a bucket, that difference lives in a policy document.

What the bucket version costs

You end up with prefixes, a policy that grants public read to one and not the other, a second policy for what your app may write, and a lambda or a queue consumer to make thumbnails. Then the rules live in three places: the policy in your cloud console, the prefix in your code, and the validation in your upload handler. The three drift, and the drift is silent, because nothing tells you when a file lands in the wrong prefix with the wrong rules.

What a template is instead

A template is a folder and a policy and a transformation in one object, and it is a POST:

# Photos: public, resized, images only
curl -X POST https://filemon.io/api/templates \
  -H "Authorization: Bearer $FILEMON_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Photos", "accepts": "image", "width": 1200, "format": "webp"}'

# Receipts: private, PDFs only, kept as they arrived
curl -X POST https://filemon.io/api/templates \
  -H "Authorization: Bearer $FILEMON_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Receipts", "accepts": "doc", "extensions": ["pdf"], "public": false}'

That is the whole configuration. Uploading to the first gives a URL you can put in a page. Uploading to the second gives a URL that answers 404 to anyone without your key.

The rules are enforced where the bytes are

accepts and extensions are not checked against the filename. A PDF renamed invoice.png is still a PDF, because the bytes carry a signature and that is what gets read. Upload one to the Photos template and you get:

{ "error": "Photos accepts .jpg, .jpeg, .png, .webp, .avif, .gif, .svg, .tiff" }

This is the part that is genuinely awkward to do with a bucket. Object storage does not look inside what you give it, so content checking has to happen in your upload handler, which means it is your job to keep it consistent with the policy that decides who can read the result.

Documents get thumbnails too

A PDF in a template gets a preview like any other file, rendered from its first page:

https://filemon.io/api/m1o900r4Nv82PqLd/preview

So a list of receipts can look like a list of receipts, rather than a list of filenames, without a queue, a worker or a rendering service in your stack.

The one rule to remember

Visibility belongs to the template, never to the individual file. If a document must be private, the template it lands in must be private, and it stays private including its original and its thumbnail. There is no per-file exception, which means there is no per-file mistake.

Try it on your own files

Filemon resizes and converts on upload, so the URL you get back is already the finished file. The free plan needs no card.

Create an account · Read the documentation