Back

Blog details

What Is CORS and Why It Matters for Object Storage

AIOZ Network
8 min readSeptember 10, 2026
aioz-storage
What is CORS and why it matters for object storage

CORS (cross-origin resource sharing) is the browser mechanism that lets a server tell a web page from another origin that it may read the server's responses. For object storage, it decides whether JavaScript on your site can upload to or read from a bucket directly.

TL;DR:

  • Browsers enforce CORS. By default, a script on one origin can't read responses from another origin unless that server says it may.
  • Before a cross-origin PUT, or any request with custom headers, the browser sends an OPTIONS preflight and waits for the server's answer before it sends the real request.
  • A bucket's CORS configuration lists allowed origins, methods and headers, plus optional exposed headers and a preflight cache time. Exposing ETag matters for browser multipart uploads.

Updated Nov 4, 2026: rewrote the post answer-first and added simple vs preflighted requests, a real preflight exchange with the AIOZ Storage endpoint, a sample configuration, ExposeHeaders and ETag, common CORS errors and a preflight diagram.

What Is CORS?

CORS is a set of HTTP headers that lets a server relax the browser's same-origin policy for the origins it trusts. An origin is the scheme, host and port of a URL together (RFC 6454), so https://app.example.com and https://s3.aiozstorage.network are different origins. When your script on the first one calls the second, the browser either blocks the call or hides the response, unless the second origin sends back the right Access-Control-Allow-Origin header.

AWS's documentation puts it this way: CORS "defines a way for client web applications that are loaded in one domain to interact with resources in a different domain." The browser is the one that enforces it (MDN CORS guide). Tools like curl and server-side SDKs don't apply CORS at all, so it never replaces authentication on your bucket.

Why Does Object Storage Need CORS?

Your web app and your storage endpoint almost always live on different origins. AWS gives the classic case: a site hosted in one S3 endpoint that wants to make "authenticated GET and PUT requests against the same bucket by using the Amazon S3 API endpoint." In AWS's words, "A browser would normally block JavaScript from allowing those requests, but with CORS you can configure your bucket to explicitly enable cross-origin requests" from the website endpoint.

Not every browser request needs it, though. A plain <img> tag can show an image from your bucket without CORS, because the page never reads the image data. MDN lists the cases that do use CORS, and several of them come up with storage:

  • fetch() or XMLHttpRequest calls, including direct uploads from the browser.
  • Web fonts loaded through @font-face from a bucket or CDN.
  • Images or video frames drawn onto a canvas when your code then reads the pixels.
  • WebGL textures, and JavaScript loaded as a module script.
Close-up of code displayed on a computer screen

How Does a CORS Preflight Request Work?

Some requests go straight through, and MDN calls them simple requests. A simple request uses GET, HEAD or POST, sets only a few safelisted headers, and sends a Content-Type of application/x-www-form-urlencoded, multipart/form-data or text/plain. Anything else gets a preflight first, and that includes every PUT upload and any request that carries an Authorization or x-amz-* header.

The preflight is an OPTIONS request that asks the server whether the real request would be allowed. It carries three headers: Origin, Access-Control-Request-Method and Access-Control-Request-Headers. We sent one to the AIOZ Storage S3 endpoint on Nov 4, 2026, for a PUT from a test origin, and this is the real exchange:

OPTIONS /test-bucket-cors-probe/hello.txt HTTP/2
Host: s3.aiozstorage.network
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type

HTTP/2 204
access-control-allow-credentials: true
access-control-allow-headers: content-type
access-control-allow-methods: PUT
access-control-allow-origin: https://app.example.com
vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers

The 204 answer allows the origin, the method and the header, so a browser would now send the real PUT. If any of the three had been refused, the browser would stop there and your upload code would see a network error. The real response then needs its own Access-Control-Allow-Origin header, or your script still can't read it.

Sequence diagram of a CORS preflight: the browser sends OPTIONS with Origin and Access-Control-Request headers, the storage endpoint answers 204 with Access-Control-Allow headers, then the browser sends the real PUT and JavaScript reads only safelisted or exposed response headers
Steps 1 and 2 show the headers the AIOZ Storage endpoint returned on Nov 4, 2026. Steps 3 and 4 follow the MDN CORS guide.

What a CORS Configuration Is Made Of

On Amazon S3, a bucket's CORS configuration is a list of rules, and you can add up to 100 of them. AWS documents five elements for each rule (elements of a CORS configuration):

  • AllowedOrigins: The origins that may send cross-origin requests. Each origin string can hold one * wildcard, as in http://*.example.com, and a lone * allows every origin.
  • AllowedMethods: Any of GET, PUT, POST, DELETE and HEAD. No other method is valid here.
  • AllowedHeaders: The headers a preflight may ask for in Access-Control-Request-Headers. Each entry can hold one wildcard, so x-amz-* covers every Amazon-specific header.
  • ExposeHeaders: Response headers your JavaScript may read.
  • MaxAgeSeconds: How long, in seconds, the browser may cache a preflight answer.

A rule for browser uploads from one app could look like this, written in the JSON form that the S3 console uses:

[
  {
    "AllowedOrigins": ["https://app.example.com"],
    "AllowedMethods": ["GET", "PUT", "POST"],
    "AllowedHeaders": ["content-type", "x-amz-*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

ExposeHeaders and the ETag problem

By default, a script can read only seven response headers: Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma (MDN safelisted response headers). ETag isn't on that list. That breaks multipart uploads run from the browser, because your code must collect each part's ETag to complete the upload. Add ETag to ExposeHeaders and the browser lets your script read it.

Common CORS Errors on Object Storage

AWS's troubleshooting page groups S3 CORS failures into a few cases, and most problems you hit will match one of them:

  • 403 CORS is not enabled for this bucket: The bucket has no CORS configuration at all.
  • 403 This CORS request is not allowed: A rule exists, but no rule matches the request's origin, method or requested headers.
  • Headers not found in the CORS response: The request worked, but your script can't see a header you need because the rule doesn't expose it.

Two more causes sit on the browser side. A wrong scheme counts as a different origin, so http://app.example.com won't match a rule for https://app.example.com. And because the browser caches a preflight answer for up to MaxAgeSeconds, a rule you just fixed can look broken until that cache expires.

A Real Example: Direct Browser Uploads

The most common reason to set CORS on a bucket is letting a browser upload straight to it with a presigned URL. Your server signs a short-lived URL, and the browser sends the file to storage without passing it through your backend. That takes load off your servers and lets large files go straight to the bucket.

The flow works only when CORS allows your app's origin, the PUT or POST method the upload uses, and every header your upload code sends. A valid presigned URL proves the request is authorized, but it says nothing about whether the browser may make a cross-origin call. Both checks have to pass, so your bucket's CORS rules and your signing code both need attention.

How AIOZ Storage Handles CORS

AIOZ Storage serves the S3 API at https://s3.aiozstorage.network, and our preflight test above shows what that endpoint returned. It allowed the requesting origin, the PUT method and the content-type header. A plain GET to the same endpoint listed Etag among its exposed headers, so a browser script could read it. We sent these requests without keys and to a bucket we don't own, so they show the endpoint's behavior, not a rule we configured.

What the docs don't cover yet:

  • CORS itself. We searched every page of AIOZ's docs, including the S3 compatibility page and the bucket tutorials, and found no mention of CORS.
  • Whether you can set or read per-bucket rules through the S3 PutBucketCors and GetBucketCors calls.

Test your own origin and methods with a real bucket before you depend on browser uploads in production. Next step: read AIOZ Storage's S3 compatibility page to see which S3 tools you can point at the endpoint.

Frequently Asked Questions

What is CORS in simple terms?
It's a browser rule that stops JavaScript on one origin from reading responses from another origin, unless that other origin allows it through CORS response headers.

Why do object storage buckets need CORS configured?
Your web app and your bucket's API endpoint are different origins, so any browser code that uploads to or reads from the bucket directly gets blocked until CORS allows it.

What is a CORS preflight request?
It's an OPTIONS request the browser sends before a non-simple cross-origin request, asking whether the origin, method and headers of the real request are allowed.

Which HTTP methods can an S3 CORS rule allow?
GET, PUT, POST, DELETE and HEAD, per AWS's documentation of the AllowedMethods element.

What does ExposeHeaders do in a CORS configuration?
It lists extra response headers your JavaScript may read. Without it, scripts see only the seven safelisted headers, which leaves out ETag.

Does a presigned URL upload still need CORS?
Yes. The presigned URL handles authorization, while CORS decides whether the browser may make the cross-origin request at all, and both have to pass.

Is CORS a security control for my bucket?
Not on its own. Browsers enforce it, but curl and server-side SDKs ignore it, so your bucket still needs keys, access grants or presigned URLs to control access.

Does AIOZ Storage support CORS configuration?
AIOZ's docs don't mention CORS. In our Nov 4, 2026 test, the S3 endpoint allowed a preflight from a test origin, but per-bucket CORS rules are not documented.

References

We only send updates when meaningful changes ship, and you can unsubscribe anytime

Related Content

blog thumbnail

What Is Distributed Storage? How It Works, With Examples

Distributed storage keeps data on many independent nodes that act as one system. See how it works, replication vs erasure coding, its types and trade-offs.

aioz-storage
6 min readNovember 03, 2026
blog thumbnail

What Are Egress Fees? How Download Charges Work in Storage

Egress fees are charges for data leaving a storage provider's network. See what triggers them, how to estimate them, and what AIOZ Storage's docs say.

aioz-storage
6 min readOctober 30, 2026
blog thumbnail

Object Storage Vector Database: Where AIOZ Storage Fits

An object storage vector database keeps vectors in the storage layer, not a cluster. See how it differs, when to pick it, and where AIOZ Storage fits in.

aioz-storage
10 min readOctober 27, 2026
blog thumbnail

AIOZ Storage Vector Database Pricing: What You Pay For

AIOZ Storage vector database pricing line by line: upload, storage, queries, data searched and data returned, with a worked monthly example you can reuse.

aioz-storage
5 min readOctober 23, 2026
blog thumbnail

How to Create a Bucket and Upload a File on AIOZ Storage

Create a bucket and upload a file on AIOZ Storage in five steps: sign up, set a passphrase, add credits, and upload. Plus what the passphrase cannot recover.

aioz-storage
5 min readOctober 20, 2026
blog thumbnail

AIOZ Storage Rate Limit: Working Within 100 Requests/s

The AIOZ Storage rate limit is 100 requests per second. See what the docs do and don't say, and how to stay under it with boto3 retries and a limiter.

aioz-storage
7 min readOctober 16, 2026