Use Filestack as Free Image Storage and CDN for Your Side Project

Posted on
Use Filestack as Free Image Storage and CDN for Your Side Project

A side project needs image storage and something to serve those images fast. The usual answer is a bucket, a distribution in front of it, an origin access policy and a cache invalidation step you get wrong once. Filestack collapses that into an upload that returns a handle and a URL that is already on a CDN, so the same account acts as both halves. Use the free plan and it costs nothing to run.

Key takeaways

  • Filestack returns a handle on upload, and that handle is a public CDN URL with no bucket or distribution behind it.
  • Store the handle rather than the URL, because handles never change while URLs get rebuilt on every width or format change.
  • One stored original serves every size, since resize and format tasks run at request time and no variant is stored.
  • The free plan’s 1 GB of bandwidth covers roughly 89,000 thumbnail requests but only about 4,000 full width ones.
  • Transformations count distinct URLs rather than visits, so standardising on a few widths keeps that counter low.

What you skip

You skip the bucket, the distribution in front of it, the origin access policy and the cache invalidation step. Upload a file and you get a 20 character handle back, which is a public URL immediately:

https://cdn.filestackcontent.com/HANDLE

There is no bucket to create, no distribution to point at it, no origin policy, and no build step that generates image variants. Sizes are produced at request time from tasks in the URL, so you never store a thumbnail and never invalidate one.

Where that trade lands against running your own bucket, and when a project has grown past it, is the subject of storage and cdn economics for startups.

Getting images in

For a project where you upload the assets yourself, one POST per file:

curl -X POST -F "fileUpload=@photo.jpg" \
  "https://www.filestackapi.com/api/store/S3?key=YOUR_API_KEY"

which returns:

{
  "url": "https://cdn.filestackcontent.com/0J0PpoBYScqJrHF8lbrO",
  "size": 107013,
  "type": "image/jpeg",
  "filename": "photo.jpg"
}

For a project where users upload, load filestack-js, which was on 3.51.6 as of 6 August 2026, and open the picker. That is three lines of JavaScript and it brings drag and drop, chunked uploads and retries with it.

Either way, store the handle in your database rather than the URL. Handles never change. URLs get rebuilt every time you change a width or a format, and a column full of stale URLs is the thing you will have to migrate later.

Serving the right size to each device

One handle serves every size. The width lives in the URL:

https://cdn.filestackcontent.com/resize=width:480/output=format:webp/T5T2GRrxSjeNBbsIMaA8
A lighthouse photograph delivered at 480 pixels wide as WebP
A lighthouse photograph delivered at 480 pixels wide as WebP, generated at request time from a single stored file

 

That means a srcset is a template rather than a build artifact:

<img
  src="https://cdn.filestackcontent.com/resize=width:768/output=format:webp/HANDLE"
  srcset="https://cdn.filestackcontent.com/resize=width:320/output=format:webp/HANDLE 320w,
          https://cdn.filestackcontent.com/resize=width:480/output=format:webp/HANDLE 480w,
          https://cdn.filestackcontent.com/resize=width:768/output=format:webp/HANDLE 768w"
  sizes="(max-width: 600px) 100vw, 768px"
  alt="">

One 319,136 byte photograph weighs this much at each of those widths, measured on 6 August 2026:

Width JPEG WebP
320 px 45,718 B 36,680 B
480 px 97,561 B 77,126 B
768 px 252,005 B 198,180 B
1024 px 377,446 B 322,092 B

 

Two things fall out of that table. WebP is consistently around a fifth smaller, which is why the format conversion is worth adding to every URL; the differences between formats and when each one wins are covered in the guide on when to convert to webp. And width is the lever that actually matters, since 320 pixels costs an eighth of what 1024 does.

Serving a 1024 pixel image into a 320 pixel slot is the single most common way a side project burns its bandwidth allowance.

Join the Filestack developer community on Discord

The arithmetic that decides whether this works

The free plan includes 1 GB of bandwidth a month. Whether that is generous or tight depends entirely on what you serve, and dividing 1 GB by the sizes measured above is worth doing before you build:

What you serve Requests inside 1 GB
A 320 px WebP thumbnail about 89,000
A 768 px WebP hero image about 26,000
A 1600 px WebP full bleed image about 4,000
The untouched 319,136 byte original about 3,300

 

A gallery of thumbnails is nowhere near the limit. A landing page shipping full size originals reaches it in a few thousand views. The fix in almost every case is the width in the URL rather than a bigger plan.

Storage is a separate 1 GB, and it counts originals only. The variants your srcset produces are not stored, so 500 photographs at 1 MB each is 500 MB no matter how many sizes you serve from them.

That asymmetry is the useful thing to design around. Uploading is where you spend storage, and it happens once per asset. Serving is where you spend bandwidth, and it happens on every page view. A project with a few hundred images and steady traffic will meet the bandwidth line long before the storage line, which means the width in your srcset is a more valuable thing to tune than how many files you keep.

Cache, so the same image is not recomputed

The default response carries cache-control: public, max-age=2667950, which is about a month. Once an edge has served a transformation, repeat requests come from the edge and the transformation does not rerun.

Set a shorter expiry when the content changes:

https://cdn.filestackcontent.com/cache=expiry:3600/resize=width:480/HANDLE

The Filestack CDN then returns cache-control: public, max-age=3600 on that URL. Caching also protects your transformation quota, since 1,000 transformations a month sounds small until you notice that a cached variant is not a new transformation. It is distinct URLs that count, so the number to keep an eye on is how many widths and formats you generate, not how many visitors you get. The mechanics of what the edges hold and for how long are in this guide to file delivery across regions.

The tasks worth knowing for a project like this

Everything below runs on a free key, and everything chains left to right in one URL:

  • resize with a width, a height, or both plus fit to control what happens to the mismatch.
  • crop with dim:[x,y,width,height] when you know the exact rectangle you want.
  • output=format:webp or jpg, applied after the resize so the encoder works on fewer pixels.
  • compress and quality for the last few percent once dimensions are settled.
  • watermark to composite one handle over another.
  • blur_faces, crop_faces and pixelate_faces, which is face detection you can call from a URL. Building something around it is covered in the walkthrough on how to blur faces in a Node and React app.

The full parameter set for each is in the image editing api guide.

What the failures look like

Three go wrong often enough to be worth recognising on sight.

A blocked operation. The API names the task, so there is no guessing which one:

You don’t have permission to perform this task: ocr. Please check your access settings

Thirteen operations answer this way on a free key: smart_crop, enhance, upscale, redeye, tags, sfw, caption, ocr, copyright, doc_detection, image_sentiment, text_sentiment, and video_convert. The rule behind the split is easy to hold in your head. If an operation has to understand what is inside a file, it is on a higher plan. If it changes the file’s shape, size or format, it is on the free one. Of 65 operations tested in July 2026, 50 run on the free plan.

A bad key. The upload POST fails rather than the delivery URL, since delivery URLs carry no key. Check that the key belongs to the application you think it does before assuming the request is malformed.

A wrong MIME type. An image task pointed at a document returns an error rather than a converted file. The tell is that the status is not 200 and the response body is text rather than bytes.

Two things to decide before you ship

Uploads are public by default. Anyone with the handle can read the file. That is correct for a portfolio, a gallery or a blog’s images, and wrong for anything private, where you want a signed policy instead. Decide which of those you are building before people put files in it.

Handles do not expire. Deleting is an explicit call, so a project that uploads on every save will fill 500 uploads a month faster than it fills 1 GB. Overwrite the same handle instead when the file is a replacement rather than a new asset.

When the side project stops being one

Three numbers are worth computing now. Bandwidth against your page weight, uploads against how often users add files, and transformations against how many distinct variants your templates generate.

Start, at $69 a month, moves those to 75 GB, 20,000 uploads and 50,000 transformations, and adds the AI-backed operations. Current quotas for every tier are on the free plan page.

Nothing structural changes when you move up. The handles stay the same, the URLs stay the same, and folding this into a wider file delivery workflow is a matter of adding tasks to URLs you already have.

 

 

Read More →