How much does it cost to store user uploads and images?

Storing uploads costs the sum of three lines: GB stored per month, upload and read requests, and data egress when someone downloads a file. With many providers, egress outweighs storage.

Updated 7 min read

Learning objectives

After reading this article you will be able to:

  • Understand the line items behind the bill for storing user files
  • Estimate a year of uploads with a step-by-step calculation
  • Cut the cost with compression, lifecycle rules and caching
On this page

Profile pictures, chat attachments, PDF receipts, product images: almost every app ends up storing files its users send. At first the volume is small and the bill barely shows. The catch is that uploads pile up. Unlike traffic, which rises and falls, whatever you stored is still there next month, and the month after. That is why it pays to estimate a year of uploads before you pick where to keep them.

The numbers in this article are hypothetical, chosen to keep the math easy, and are in US dollars. They are not any provider's prices; real prices change, vary by region and are on the official pages, so check the official AWS and Backblaze pages before you decide.

monthly costmonth 1month 6month 12Egressfollows viewsRequestsflatStoragegrows every month
Each month adds new files to the old ones, so storage only grows; egress follows the views.

What makes up the cost of storing uploads?#

In object storage, which is where uploads usually live (see what is object storage), the bill has up to three lines:

  • Storage: a price per GB stored per month. This is the line that grows on its own, because each month adds new files on top of the old ones.
  • Requests: every upload (PUT) and every read (GET) may be billed, usually per thousand. It matters in apps with many small files, such as thumbnails.
  • Egress: a price per GB that leaves storage for the internet when someone opens the file. With several providers, it is the most expensive line. The article on egress fees explains how it works.

Outside storage, there is processing (generating thumbnails uses your server's CPU) and, if you use one, the CDN in front of public files.

How do providers bill each line?#

Two billing models, with the hypothetical values used in the calculation further down:

Item (hypothetical value) Provider A: bills each line Provider B: egress allowance
Storage $0.02 per GB/month $5 per TB/month
Uploads (PUT, COPY, POST, LIST) $0.01 per 1,000 No charge
Reads (GET) $0.001 per 1,000 No charge
Egress to the internet First 100 GB/month at no charge, then $0.10 per GB No charge up to 2 times your stored data, then $0.02 per GB

Both designs exist in the market. S3 bills each line separately and offers cheaper classes for rarely accessed files, like provider A. B2 has cheaper storage per GB and bundles requests and part of the egress, like provider B. Neither is cheaper in every case: the answer depends on how much you store and how much gets downloaded.

How do you estimate your upload volume?#

Do the math with four numbers:

GB per month = uploads per month × average size × number of stored versions
GB stored in month N = GB per month × N

What moves the bill most is the number of versions. Many apps store the original upload, a display version and a thumbnail. A phone photo is a few MB; the same image resized to WebP for display is a few hundred KB, and the thumbnail a few dozen.

Hypothetical example: an app with 5,000 active users, each uploading 10 photos per month. Each upload produces a 3 MB original, a 300 KB display version and a 40 KB thumbnail. Every month the app serves 1 million views of the large version and 3 million thumbnails.

The script below calculates the bill for month 12 and runs on any recent Node.js version:

js
// uploads-cost.mjs: node uploads-cost.mjs
// Hypothetical example in USD: provider A bills each line; provider B has an egress allowance.
// These are not any provider's prices; swap in the official ones before relying on the result.
const uploadsPerMonth = 50_000; // 5,000 users x 10 photos
const months = 12;
const mbPerUpload = 3 + 0.3 + 0.04; // original + display version + thumbnail
const putsPerUpload = 3;
const getsPerMonth = 4_000_000; // 1 million views + 3 million thumbnails
const gbDownloadedPerMonth = 1_000_000 * 0.0003 + 3_000_000 * 0.00004; // 420 GB

const gbStored = (uploadsPerMonth * mbPerUpload * months) / 1000;

const providerA = {
  storage: gbStored * 0.02,
  requests:
    ((uploadsPerMonth * putsPerUpload) / 1000) * 0.01 +
    (getsPerMonth / 1000) * 0.001,
  egress: Math.max(0, gbDownloadedPerMonth - 100) * 0.1,
};
const providerB = {
  storage: (gbStored / 1000) * 5, // $5 per TB-month
  requests: 0, // requests are not billed
  egress: Math.max(0, gbDownloadedPerMonth - 2 * gbStored) * 0.02, // allowance of 2x stored
};

console.log(`Stored in month ${months}: ${gbStored.toFixed(0)} GB`);
for (const [name, c] of Object.entries({ "Provider A": providerA, "Provider B": providerB })) {
  const total = c.storage + c.requests + c.egress;
  console.log(
    `${name}: storage $${c.storage.toFixed(2)}, ` +
      `requests $${c.requests.toFixed(2)}, ` +
      `egress $${c.egress.toFixed(2)}, total $${total.toFixed(2)}`,
  );
}

The result, with about 2 TB stored in month 12:

Provider Storage Requests Egress Month 12 total
Provider A $40.08 $5.50 $32.00 $77.58
Provider B $10.02 $0.00 $0.00 $10.02

These numbers come from the hypothetical values, before taxes and currency conversion. At provider B, egress is free of charge here only because the downloaded volume (420 GB) is less than 2 times the stored amount; in month 1, with only 167 GB stored, the allowance would be 334 GB and the remaining 86 GB would be billed.

What weighs most: storage, requests or egress?#

Run the same script without keeping the original (change mbPerUpload to 0.3 + 0.04). Storage in month 12 drops from 2,004 GB to 204 GB, and the provider A total drops from $77.58 to $41.58. Egress stays at $32.00, because visitors never downloaded the original.

The example leaves three takeaways:

  1. The original is the biggest storage cost. It takes almost 90% of the space in the example and is never shown to anyone.
  2. Egress follows views, not stored volume. In a popular app it easily overtakes storage, with providers that charge for egress.
  3. Requests rarely dominate, unless you have millions of tiny files or repeated reads without a cache.

What hidden costs show up?#

  • Orphaned files. A user deletes their account or changes their photo, and the old file stays in the bucket forever. Delete the object together with its database record.
  • Incomplete multipart uploads. In large uploads split into parts, the parts of an abandoned upload keep taking up space. AWS recommends a lifecycle rule with AbortIncompleteMultipartUpload to delete them after a few days. See how to upload large files.
  • Versioning turned on. With versioning, overwriting a file does not delete the previous version, and every version is billed.
  • Minimum duration in cold classes. Infrequent-access classes charge a minimum storage time (30 days on S3 Standard-IA, 90 to 180 days on the Glacier classes). Moving files there that will be deleted soon can cost more.
  • Images in the database or on the server disk. Storing bytes in the database makes disk and dumps more expensive; storing them on the app's local disk ties the files to one machine. Compare in images in a database vs object storage and object storage vs local disk.
  • Hotlinking. Another site using the direct link to your images creates egress on your bill.

How do you reduce the cost of storing uploads?#

In order of payoff:

  1. Resize and compress on upload. Generate the display version in WebP or AVIF with a maximum width, and decide whether the original really needs to stay.
  2. Limit file size and type before accepting the upload. A 10 MB cap and a list of allowed extensions keep 2 GB videos out of a profile picture field.
  3. Use lifecycle rules to delete temporary files, move originals to a cheaper class and clean up incomplete uploads. See object lifecycle rules.
  4. Put a cache in front of public files, with a long Cache-Control on files that never change, so egress from storage happens once per file instead of once per visit.
  5. Upload straight from the browser to storage with a presigned URL, so the file never passes through your server or uses its bandwidth.
  6. Pick the provider by the line that weighs most for you. Lots of views call for cheap egress; lots of stored volume calls for cheap storage. The article on object storage cost compares pricing models.

How to do this on Square Cloud#

On Square Cloud, uploads can go to Blob Storage, an object storage with an S3-compatible API (aws-cli, rclone, boto3 and the AWS SDKs), a REST API and a dashboard. It charges no egress or ingress fees, so the egress line from the example doesn't exist here: downloads carry no bandwidth charge. The limit that does apply is a per-plan rate limit on S3 requests.

Every plan includes a Blob quota, from 5 GB to 250 GB. Hobby, from R$12.49 per month, includes 5 to 15 GB. Public files are served by a global edge CDN; private files open through a temporary link of up to 24 hours or a revocable share link of up to 30 days.

Per-folder rules cover much of this article: maximum size, allowed extensions, expiration, caching and automatic cleanup. Files can be up to 10 GB, and multipart is only required above 100 MB. See the details on the Blob Storage page.

Blob Storage

S3-compatible object storage with no egress fees

Learn more

FAQs

How much does it cost to store 1 TB of images per month?

Storage alone is the volume in GB times the provider's price per GB-month. Assuming $0.02 per GB-month (a hypothetical value), 1 TB (1,000 GB) comes to $20 per month; at a hypothetical $5 per TB-month, it would be $5. Requests and egress are billed on top.

Is it cheaper to store images in the database?

Almost never. Database disk usually costs more per GB than object storage, images bloat your dumps and every read goes through the database. The usual pattern is to keep the file in object storage and only its key in the database.

What is the biggest part of an uploads bill?

It depends on usage. In an app with many views, egress is often the largest line with providers that charge for it. In an app that keeps large originals and rarely shows them, accumulated storage dominates.

Should I keep the original photo the user uploaded?

Only if you need it, for example to reprocess later. A phone photo is a few MB, while a WebP display version can be a few hundred KB. Dropping the original or moving it to a cheaper storage class removes most of the storage cost.

Next articleObject storage vs local disk: where to save your app's filesLocal disk is fast but tied to one machine; object storage is reachable from anywhere. Compare the two side by side and learn when to use each one.

More in Storage

See all 15 articles