Skip to content

Amazon S3 Integration ​

Amazon 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 below).

CSP ​

If your site uses a Content Security Policy (CSP), you need to allow the S3 endpoint. See 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. 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. 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:

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
[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
{
  "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
{
  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 ​

PropertyRequiredDescription
access_key_idYesAWS Access Key ID (safe to store in config).
bucketYesThe S3 bucket name.
regionYesAWS region, e.g. us-east-1, eu-west-1. With a custom endpoint, the region of your server; see Self-Hosted Storage below.
prefixNoPath prefix within the bucket, e.g. uploads/. A trailing slash is added if missing.
force_path_styleNoUse 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.
endpointNoCustom endpoint URL for another S3-compatible service, such as Garage or MinIO, e.g. https://s3.example.com. Objects are addressed as {endpoint}/{bucket}/{key}. See Self-Hosted Storage below.
public_urlNoCustom domain for asset URLs (e.g. a CloudFront distribution). See 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 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 or MinIO, for organizations that must keep their assets on their own infrastructure. Set endpoint to the URL of the server’s S3 API:

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
[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
{
  "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
{
  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 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 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 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 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 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 and 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. 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:

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