---
url: /en/docs/media/amazon-s3.md
description: >-
  Use Amazon S3 as an external media storage provider in Sveltia CMS for
  scalable, cloud-based asset management.
---

# Amazon S3 Integration

[Amazon S3](https://aws.amazon.com/s3/) (Simple Storage Service) is a scalable object storage service provided by AWS. Sveltia CMS supports S3 as a media storage backend with direct browser-to-S3 uploads using AWS Signature Version 4 — no backend proxy is required.

## Requirements

* An AWS account with an S3 bucket created.
* An IAM user with an access key and the minimum required permissions (see [Credentials](#credentials) below).

### CSP

If your site uses a Content Security Policy (CSP), you need to allow the S3 endpoint. See [Content Security Policy](#content-security-policy) below for details.

## Setup

### Credentials

Create an IAM user with programmatic access and attach a policy granting the minimum required permissions:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"]
    }
  ]
}
```

The `s3:DeleteObject` action is only needed to delete and rename files and folders from the [Asset Library](/en/docs/ui/asset-library). Leave it out if you don’t want editors to remove files. Renaming copies the object to the new key and then deletes the original. An empty folder is kept with a zero-byte placeholder object.

The resulting **Access Key ID** goes in `access_key_id` in your config. The **Secret Access Key** is entered by users in the CMS UI when they access the media library for the first time — it is never stored in config.

### Public Read Access

Asset preview and download URLs are unsigned direct storage URLs, so objects must be publicly readable. Sveltia CMS doesn’t set object ACLs, which new buckets have disabled by default, so grant public read access with a bucket policy instead:

1. In the S3 console, open the bucket’s **Permissions** tab.
2. Under **Block Public Access**, disable `BlockPublicPolicy` and `RestrictPublicBuckets`.
3. Add a bucket policy granting `s3:GetObject` to `"Principal": "*"` for the objects path:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/cms-uploads/*"
    }
  ]
}
```

### CORS

Configure cross-origin resource sharing in the S3 console under **Bucket > Permissions > Cross-origin resource sharing (CORS)**. CORS is required because Sveltia CMS sends custom AWS Signature v4 headers that trigger a preflight request.

```json
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "DELETE", "HEAD"],
    "AllowedOrigins": ["https://your-cms-domain.com"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]
```

The `DELETE` method and the wildcard `AllowedHeaders` are needed to delete and rename files from the [Asset Library](/en/docs/ui/asset-library). If you configured CORS before these operations became available, add them to your existing policy. Otherwise, the browser will block those requests during the preflight check.

### Bucket Naming

For virtual-hosted-style URLs (the default), the bucket name must be DNS-compatible: lowercase letters, numbers and hyphens only. If your bucket name contains underscores, use `force_path_style: true` in your config.

## Configuration

Here’s an example configuration for Amazon S3:

::: code-group

```yaml [YAML]
media_libraries:
  aws_s3:
    access_key_id: AKIAIOSFODNN7EXAMPLE
    bucket: my-website-assets
    region: us-east-1
    prefix: cms-uploads/ # Optional
    force_path_style: false # Optional, see Bucket Naming above
    public_url: https://media.example.com # Optional, see Custom Domain below
```

```toml [TOML]
[media_libraries.aws_s3]
access_key_id = "AKIAIOSFODNN7EXAMPLE"
bucket = "my-website-assets"
region = "us-east-1"
prefix = "cms-uploads/"
force_path_style = false
public_url = "https://media.example.com"
```

```json [JSON]
{
  "media_libraries": {
    "aws_s3": {
      "access_key_id": "AKIAIOSFODNN7EXAMPLE",
      "bucket": "my-website-assets",
      "region": "us-east-1",
      "prefix": "cms-uploads/",
      "force_path_style": false,
      "public_url": "https://media.example.com"
    }
  }
}
```

```js [JavaScript]
{
  media_libraries: {
    aws_s3: {
      access_key_id: 'AKIAIOSFODNN7EXAMPLE',
      bucket: 'my-website-assets',
      region: 'us-east-1',
      prefix: 'cms-uploads/', // Optional
      force_path_style: false, // Optional
      public_url: 'https://media.example.com', // Optional
    },
  },
}
```

:::

::: warning

Do not write your Secret Access Key in the configuration file, as it should be kept confidential and not exposed in client-side code. Users will be prompted to enter the key when they use the storage for the first time. It’s then stored securely in the browser’s local storage.

:::

### Configuration Properties

| Property | Required | Description |
| --- | --- | --- |
| `access_key_id` | Yes | AWS Access Key ID (safe to store in config). |
| `bucket` | Yes | The S3 bucket name. |
| `region` | Yes | AWS region, e.g. `us-east-1`, `eu-west-1`. With a custom `endpoint`, the region of your server; see [Self-Hosted Storage](#self-hosted-storage) below. |
| `prefix` | No | Path prefix within the bucket, e.g. `uploads/`. A trailing slash is added if missing. |
| `force_path_style` | No | Use path-style URLs (`s3.region.amazonaws.com/bucket`) instead of virtual-hosted-style (`bucket.s3.region.amazonaws.com`). Defaults to `false`. Has no effect with `endpoint`, which always uses path-style URLs. |
| `endpoint` | No | Custom endpoint URL for another S3-compatible service, such as [Garage](https://garagehq.deuxfleurs.fr/) or [MinIO](https://www.min.io/), e.g. `https://s3.example.com`. Objects are addressed as `{endpoint}/{bucket}/{key}`. See [Self-Hosted Storage](#self-hosted-storage) below. |
| `public_url` | No | Custom domain for asset URLs (e.g. a CloudFront distribution). See [Custom Domain](#custom-domain) below. |

## Custom Domain

If you serve assets through a CloudFront distribution or a Route 53 custom domain pointing to your S3 bucket, set `public_url` to that URL. Asset preview and download URLs in the CMS will use the custom domain instead of the S3 endpoint:

```yaml
public_url: 'https://media.example.com'
```

The S3 API endpoint is still used for listing and uploading — only the asset URLs shown in the CMS change. See the [Route 53 getting-started guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/getting-started-s3.html) for setting up a custom domain with S3.

## Self-Hosted Storage

The Amazon S3 integration also works with a self-hosted, S3-compatible object storage server, such as [Garage](https://garagehq.deuxfleurs.fr/) or [MinIO](https://www.min.io/), for organizations that must keep their assets on their own infrastructure. Set `endpoint` to the URL of the server’s S3 API:

::: code-group

```yaml [YAML]
media_libraries:
  aws_s3:
    endpoint: https://s3.example.com
    region: garage
    access_key_id: GK31c2f218a2e44f485b94239e
    bucket: my-website-assets
    public_url: https://my-website-assets.web.example.com # Optional, see below
```

```toml [TOML]
[media_libraries.aws_s3]
endpoint = "https://s3.example.com"
region = "garage"
access_key_id = "GK31c2f218a2e44f485b94239e"
bucket = "my-website-assets"
public_url = "https://my-website-assets.web.example.com"
```

```json [JSON]
{
  "media_libraries": {
    "aws_s3": {
      "endpoint": "https://s3.example.com",
      "region": "garage",
      "access_key_id": "GK31c2f218a2e44f485b94239e",
      "bucket": "my-website-assets",
      "public_url": "https://my-website-assets.web.example.com"
    }
  }
}
```

```js [JavaScript]
{
  media_libraries: {
    aws_s3: {
      endpoint: 'https://s3.example.com',
      region: 'garage',
      access_key_id: 'GK31c2f218a2e44f485b94239e',
      bucket: 'my-website-assets',
      public_url: 'https://my-website-assets.web.example.com', // Optional, see below
    },
  },
}
```

:::

Keep the following in mind:

* **Path-style URLs:** With a custom `endpoint`, objects are always addressed as `{endpoint}/{bucket}/{key}`, so the server doesn’t need a wildcard DNS record or a TLS certificate for bucket subdomains. `force_path_style` has no effect.
* **Region:** The region isn’t part of the URL, but it’s used to sign every request. It must therefore match the region the server is configured with. For Garage, that’s the `s3_region` option (`garage` in the sample configuration). For MinIO, it’s `us-east-1` unless you’ve changed it. A mismatch makes every request fail with a signature error.
* **CORS:** The server must allow cross-origin requests from the CMS origin, as described in [CORS](#cors) above. With Garage, apply the same policy to the bucket through its S3 API, e.g. with `aws s3api put-bucket-cors --endpoint-url https://s3.example.com`. MinIO allows any origin by default. If you’ve restricted it, add the CMS origin to its `cors_allow_origin` setting.
* **Public read access:** Asset preview and download URLs are unsigned, so objects must be readable without credentials. On MinIO, allow anonymous downloads on the bucket, e.g. with `mc anonymous set download`. Garage doesn’t serve anonymous requests on its S3 API. Instead, enable [website access](https://garagehq.deuxfleurs.fr/documentation/cookbook/exposing-websites/) on the bucket and set `public_url` to the bucket’s web endpoint or a reverse proxy in front of it.
* **HTTPS:** If the CMS is served over HTTPS, browsers block requests to a plain `http://` endpoint as mixed content, except for `localhost`. Put the server behind a reverse proxy with a TLS certificate.
* **Content Security Policy:** Add the `endpoint` origin, and the `public_url` origin if any, to `connect-src` and `img-src`. See [Content Security Policy](#content-security-policy) below.

## Content Security Policy

The URL pattern depends on whether `force_path_style` is enabled.

**Virtual-hosted-style (default)** — `https://{bucket}.s3.{region}.amazonaws.com`:

```
connect-src https://*.s3.us-east-1.amazonaws.com;
img-src     https://*.s3.us-east-1.amazonaws.com;
```

**Path-style (`force_path_style: true`)** — `https://s3.{region}.amazonaws.com/{bucket}`:

```
connect-src https://s3.us-east-1.amazonaws.com;
img-src     https://s3.us-east-1.amazonaws.com;
```

Replace `us-east-1` with your actual bucket region.

If using a custom domain via `public_url`, add it to `connect-src` and `img-src` as well:

```
connect-src https://*.s3.us-east-1.amazonaws.com
            https://media.example.com;
img-src     https://*.s3.us-east-1.amazonaws.com
            https://media.example.com;
```

The asset host must be in `connect-src` because the Download and Copy buttons and the text preview in the [Asset Library](/en/docs/ui/asset-library) fetch the file from its public URL. For the same reason, a custom domain must also send CORS headers that allow the CMS origin. Otherwise, downloads fail.

See the [CSP documentation](/en/docs/security#setting-up-content-security-policy) for more details.

## Accessing the Storage

There are two ways to use Amazon S3 in Sveltia CMS:

### File and Image Fields

When editing content entries, users can use [File](/en/docs/fields/file) and [Image](/en/docs/fields/image) fields to upload and select media on Amazon S3 directly within the entry editor. After entering the Secret Access Key in the CMS UI when prompted, users can upload new media directly to S3 or select existing media from the bucket.

When uploading media, files will be stored in the S3 bucket, and the CMS can take advantage of S3’s capabilities directly from the CMS. You can also select existing media from your S3 storage.

### Asset Library

Amazon S3 also appears under **External Locations** in the [Asset Library](/en/docs/ui/asset-library). There, users can browse, search, sort and filter the files in your bucket, and upload, rename, replace, download or delete them without leaving the CMS.

The files and folders are browsed [folder by folder](/en/docs/ui/asset-library#folders-on-external-locations):

* Subfolders are listed ahead of the files.
* Uploads go to the folder being viewed.
* Folders can be created, renamed and deleted.
