# CORS

Source: https://docs.sliplane.io/object-storage/cors

Configure CORS rules on a Sliplane Object Storage bucket so browsers on your own domains can read from and upload to it directly.

Browsers block JavaScript from reading a response that comes from a different origin than the page, unless the response says the origin is allowed. CORS rules on a bucket are what produce that permission.

You need a CORS rule when browser code talks to the bucket endpoint directly, for example:

* uploading a file straight from the browser with a presigned URL
* fetching an object with `fetch()` or `XMLHttpRequest`
* reading a response header such as `ETag` after an upload

Server-side SDKs and background jobs do not need CORS rules.

## Add a CORS rule

1. Open **Object Storage** in the dashboard and select your bucket.
2. Go to the **Settings** tab and find the **CORS** card.
3. Click **Add Rule**.
4. Fill in the rule and click **Save**.

To change a rule later, click it in the list. To delete it, use the remove button next to it and confirm.

> **info**
>
> CORS cannot be changed while a bucket is queued for deletion.

## Rule fields

| Field               | Meaning                                                                                |
| ------------------- | -------------------------------------------------------------------------------------- |
| **Allowed Origins** | Where the browser is running. Required.                                                |
| **Allowed Methods** | Which requests the browser may make: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`. Required. |
| **Allowed Headers** | Request headers the browser may send.                                                  |
| **Exposed Headers** | Response headers your JavaScript may read.                                             |
| **Max Age**         | How long the browser caches the preflight response, in seconds.                        |

### Allowed Origins

An origin is a scheme and a host, with no path, query, or fragment:

```txt
https://app.example.com
http://localhost:3000
```

Two wildcards are accepted:

* `*` allows any origin
* a leading `*.` matches all subdomains, for example `https://*.example.com`

A wildcard anywhere else is rejected. Only `http://` and `https://` origins are allowed, and a trailing slash is stripped before the origin is stored.

> **warn**
>
> `*` lets any website on the internet make the requests the rule permits. Use it for public read-only assets, not for rules that allow `PUT`, `POST`, or `DELETE`.

### Allowed Methods

Pick the methods your browser code actually issues. Reading objects needs `GET`, and usually `HEAD` as well. Uploading with a presigned URL needs `PUT`. Browser-side deletes need `DELETE`.

### Allowed Headers

These are the request headers the browser is allowed to send, the ones it asks about in the `Access-Control-Request-Headers` preflight. Uploads typically need at least `Content-Type`.

`*` allows all headers, and a trailing wildcard matches a prefix, for example `x-amz-*`. A header name cannot contain spaces or separators such as `:` or `/`.

### Exposed Headers

By default the browser only hands a few response headers to your code. Anything else has to be listed here. An upload that verifies the returned `ETag` needs `ETag` exposed.

Wildcards are **not** allowed in exposed headers, so list each header by name.

### Max Age

The number of seconds a browser may cache the preflight response before asking again. `0` leaves the decision to the browser. New rules start at `3600`.

## Example: browser uploads with presigned URLs

A single-page app on `https://app.example.com` uploads files with presigned `PUT` URLs generated by your backend, and checks the `ETag` it gets back:

| Field           | Value                     |
| --------------- | ------------------------- |
| Allowed Origins | `https://app.example.com` |
| Allowed Methods | `PUT`, `GET`, `HEAD`      |
| Allowed Headers | `Content-Type`            |
| Exposed Headers | `ETag`                    |
| Max Age         | `3600`                    |

If you also run the app locally against the same bucket, add `http://localhost:3000` as a second origin in the same rule.

## Limits

| Limit            | Value                         |
| ---------------- | ----------------------------- |
| Rules per bucket | 10                            |
| Values per field | 25                            |
| Max age          | 0 to 86400 seconds (24 hours) |

## Changes made outside the dashboard

An access key has full control over its own bucket, which includes the bucket’s CORS configuration. Any S3 client can therefore overwrite these rules with `PutBucketCors`, and delete them with `DeleteBucketCors`.

The dashboard reads the live configuration from the bucket every time you open the settings page, so rules written by an S3 client show up there and can be edited like any other rule.

## Troubleshooting

**The browser reports a CORS error.** Check the origin in the error message against the rule. The scheme and the port are part of the origin: `http://localhost:3000` does not match `https://localhost:3000` or `http://localhost`.

**A preflight request fails.** The method and every header your code sends must be covered by the same rule. A request that sends `Content-Type` needs `Content-Type` (or `*`) in Allowed Headers.

**Your code cannot read a response header.** Add the header to Exposed Headers.

**An old rule still seems to apply.** Browsers cache preflight responses for the length of Max Age. Reload with the developer tools open and caching disabled, or wait it out.

## API

See the [bucket API reference](https://ctrl.sliplane.io/#tag/buckets).
