An Optimized Image Upload Pipeline

Upload once and get resizing, compression, and location-metadata removal automatically

image optimizationWebPEXIF removalobject storageupload
6 min read

Introduction

For a rental property site, photos are everything. The trouble is that posting shots straight from a phone or camera means lining the site with 5MB files. Pages get slow, and guests browsing on mobile data pay the price.

But firing up image-editing software to resize every picture before writing a post isn't realistic either. This article covers how we built a pipeline where optimization finishes the moment you upload.

Inspecting the uploaded file

Never trust the extension

Start from the assumption that an uploaded file can't be trusted. A filename ending in ".jpg" tells you nothing about what's actually inside.

File acceptance checks
Check the size

Reject anything over the limit, and anything empty

Check the declared format

Reject anything outside the permitted image formats (JPEG, PNG, WebP, GIF, AVIF)

Actually read the contents

Parse it with an image-processing library and confirm it really is the image it claims to be

Reject on mismatch

A file that says JPEG but contains something else gets stopped right here

Step three — actually reading the contents — is the important one. Every format starts with a distinctive marker (a signature), and we verify it with the image-processing library. Files whose name and contents disagree get thrown out at this point.

We don't permit vector images (SVG) at all. SVG files can contain scripts, which makes them a viable attack route dressed up as an image.

Don't reuse the filename

When we save a file, we don't use the name it arrived with. It gets a fresh name built from a random identifier.

Reusing the original name opens the door to overwriting an existing image with a same-named file, or to a crafted name that writes to somewhere it shouldn't. The original filename is only recorded in the database so it can be displayed in the dashboard.

Optimizing automatically

Resizing and format conversion

Images that pass inspection aren't stored as-is — they're processed first.

We configured it to leave images alone if they already fit, so small originals never get forcibly enlarged. Enlarging only makes things blurry — there's nothing to gain.

Stripping location data

Photos taken on a phone often carry the latitude and longitude of where they were shot. Publish an exterior photo as-is and the exact address can be read straight out of the image file.

The image-processing library we used doesn't carry metadata across during conversion, so location data is removed automatically as part of the WebP conversion. The one piece we handle first is the capture orientation, which we apply as an actual rotation of the image before it's discarded.

Don't break animations

A GIF might be animated. Converting one to a still image would destroy the motion, so GIFs are the exception: they skip optimization and are stored exactly as uploaded.

When you automate something, spotting these "this needs to be an exception" cases is what keeps the automation trustworthy.

Choosing where files are stored

Separating public from private

Images go into object storage (a service dedicated to holding files), but where they land depends on what they're for.

Choosing a storage location
Public storage

Blog and news images. Anyone with the URL can view them

Private storage

Things like appliance manuals in PDF form. Only delivered through an authenticated request

We didn't want guest-facing documents to be one leaked URL away from being visible to everyone. They live in private storage and are handed out only to authorized people through a dedicated delivery path.

We also verify, immediately after saving, that a file really did land in the intended location. That guards against a misconfiguration dropping a private file into public storage. If it's not where we expected, we delete it right away and raise an error.

Images in use can't be deleted

Deleting an image that an article is using would leave a broken image on a live page. So before deleting, we check whether the image is referenced by any article, and refuse the deletion if it's in use.

Optimizing at display time too

On top of the optimization at save time, we also serve images sized to the viewer's screen at display time. Phones get a small version, large displays get a large one, automatically.

We also made image descriptions (alt text) editable in the dashboard in all four languages. That's the text read aloud for visitors using a screen reader, and the text shown if an image fails to load.

Wrapping up

Three points anchored the image pipeline.

  1. Inspect the contents — don't trust the extension or the declared format; read the file and confirm
  2. Uploading is the whole job — resizing, format conversion, and location-data removal all happen automatically
  3. Separate storage by purpose — what's safe to publish and what needs authentication are physically apart