---
url: /en/docs/media.md
description: >-
  Configure media storage in Sveltia CMS with internal Git storage and external
  provider integrations.
---

# Media Storage

Sveltia CMS supports multiple media storage providers for managing media assets such as images and files. You can choose from the built-in internal media storage that saves files directly in your Git repository, or integrate with popular cloud-based media storage services for enhanced capabilities.

::: tip Note for Netlify/Decap CMS users

In Sveltia CMS, the term “media storage provider” is used instead of “media library” to avoid confusion with Sveltia CMS’s [Asset Library feature](/en/docs/ui/asset-library) that lets users manage media assets from multiple sources in one place. There is no change in functionality or configuration; it’s simply a terminology update.

:::

## Internal Storage

The [internal media storage](/en/docs/media/internal) allows you to store media files directly in your Git repository along with your content files. It supports various configuration options for organizing and managing media files effectively.

## External Storage

Sveltia CMS supports integrations with popular cloud-based media storage providers for enhanced capabilities such as automatic image transformations, CDN delivery, and more. Sveltia CMS currently supports the following external media storage providers:

* [Amazon S3](/en/docs/media/amazon-s3) and S3-compatible providers:
  * [Backblaze B2](/en/docs/media/backblaze-b2)
  * [Bunny Storage](/en/docs/media/bunny-storage)
  * [Cloudflare R2](/en/docs/media/cloudflare-r2)
  * [DigitalOcean Spaces](/en/docs/media/digitalocean-spaces)
  * [Scaleway Object Storage](/en/docs/media/scaleway-object-storage)
  * [Supabase Storage](/en/docs/media/supabase-storage)
  * Any other S3-compatible service, including self-hosted servers such as [Garage](https://garagehq.deuxfleurs.fr/) and [MinIO](https://www.min.io/), through the Amazon S3 integration’s `endpoint` option. See [Self-Hosted Storage](/en/docs/media/amazon-s3#self-hosted-storage) for details.
* [Azure Blob Storage](/en/docs/media/azure-blob-storage)
* [Cloudinary](/en/docs/media/cloudinary)
* [Uploadcare](/en/docs/media/uploadcare)

Unlike backends, you can use multiple storage providers simultaneously in Sveltia CMS. Each media storage provider integration includes its own configuration instructions.

::: warning Breaking changes from Netlify/Decap CMS

Sveltia CMS does not support the deprecated **Netlify Large Media** service. If you’re using it with Netlify/Decap CMS, you will need to migrate your assets to one of the supported providers mentioned above.

Also, Sveltia CMS does not support the undocumented custom media storage provider API. The `CMS.registerMediaLibrary` method is a noop in Sveltia CMS. We may add support for custom storage providers in future releases, though compatibility with existing Netlify/Decap CMS custom media libraries is not guaranteed.

:::

::: info Future Plans

More integration options, such as Cloudflare Images, will be added in the future.

:::

## Configuration

Relevant configuration options can be set in the `media_folder`, `public_folder`, and `media_libraries` options of your CMS configuration file. The `media_library` option from Netlify/Decap CMS is also supported for backward compatibility.

The following example demonstrates how to configure multiple providers in Sveltia CMS:

::: code-group

```yaml [YAML]
# Default media storage paths
media_folder: /public/media
public_folder: /media

# Media provider features
media_libraries:
  default:
    config:
      max_file_size: 1024000 # default: Infinity
      slugify_filename: true # default: false
      # transformations: # See the documentation for details
  cloudinary:
    config:
      cloud_name: YOUR_CLOUD_NAME
      api_key: YOUR_API_KEY
    output_filename_only: true
  uploadcare:
    config:
      publicKey: YOUR_PUBLIC_KEY
    settings:
      autoFilename: true
      defaultOperations: '/resize/800x600/'
```

```toml [TOML]
# Default media storage paths
media_folder = "/public/media"
public_folder = "/media"

# Media provider features
[media_libraries.default]
[media_libraries.default.config]
max_file_size = 1024000 # default: Infinity
slugify_filename = true # default: false
# transformations: See the documentation for details

[media_libraries.cloudinary]
output_filename_only = true

[media_libraries.cloudinary.config]
cloud_name = "YOUR_CLOUD_NAME"
api_key = "YOUR_API_KEY"

[media_libraries.uploadcare]
[media_libraries.uploadcare.config]
publicKey = "YOUR_PUBLIC_KEY"

[media_libraries.uploadcare.settings]
autoFilename = true
defaultOperations = "/resize/800x600/"
```

```json [JSON]
{
  "media_folder": "/public/media",
  "public_folder": "/media",
  "media_libraries": {
    "default": {
      "config": {
        "max_file_size": 1024000,
        "slugify_filename": true
      }
    },
    "cloudinary": {
      "config": {
        "cloud_name": "YOUR_CLOUD_NAME",
        "api_key": "YOUR_API_KEY"
      },
      "output_filename_only": true
    },
    "uploadcare": {
      "config": {
        "publicKey": "YOUR_PUBLIC_KEY"
      },
      "settings": {
        "autoFilename": true,
        "defaultOperations": "/resize/800x600/"
      }
    }
  }
}
```

```js [JavaScript]
{
  media_folder: "/public/media",
  public_folder: "/media",
  media_libraries: {
    default: {
      config: {
        max_file_size: 1024000,
        slugify_filename: true,
      },
    },
    cloudinary: {
      config: {
        cloud_name: "YOUR_CLOUD_NAME",
        api_key: "YOUR_API_KEY",
      },
      output_filename_only: true,
    },
    uploadcare: {
      config: {
        publicKey: "YOUR_PUBLIC_KEY",
      },
      settings: {
        autoFilename: true,
        defaultOperations: "/resize/800x600/",
      },
    },
  },
}
```

:::

See the individual media storage provider documentation for specific configuration options and details.

The `media_libraries` option can also be defined for a [File](/en/docs/fields/file) or [Image](/en/docs/fields/image) field. The options of each provider, including the internal media storage (`default`), the stock photo providers (`stock_assets`) and the shared `all` options, are merged over the same provider’s top-level options, so a field only needs to set the options it changes, or `false` to make the provider unavailable for the field. A nested object such as `config` is merged key by key, but only one level deep, while other values, including arrays such as the `providers` list, are replaced. The `all` options are merged shallowly, too. So a field-level `transformations` map, under `all` or `default.config`, replaces the top-level one as a whole rather than being merged format by format. Providers not defined there fall back to the top-level configuration.

::: details Legacy `media_library` Option

Sveltia CMS supports the legacy `media_library` option for backward compatibility with Netlify/Decap CMS, but it is recommended to use the `media_libraries` option for new configurations. With the legacy option, only a single media storage provider can be configured. If both options define the same provider, `media_libraries` takes precedence. Unlike Netlify/Decap CMS, which requires the `name`, Sveltia CMS applies a legacy option without a `name` to the internal media storage. Here is an example of configuring Cloudinary using the legacy option:

```yaml
media_library:
  name: cloudinary
  config:
    cloud_name: YOUR_CLOUD_NAME
    api_key: YOUR_API_KEY
  output_filename_only: true
```

:::

## Additional Features

A couple of additional features are available to enhance media management. These features apply to the internal media storage and to files uploaded to external storage providers, except for Cloudinary, which uses its own Media Library widget.

The configuration goes in the `media_libraries` option, under the `all` key. For the internal media storage, these options can be overridden by the same options in `media_libraries.default.config`. A File or Image field can also have its own `media_libraries.all` options, which are merged into the global ones. For the internal media storage, the options are applied in this order, each overriding the previous ones: the top-level `all`, the top-level `default.config`, the field-level `all` and the field-level `default.config`. So a field-level `all` option takes precedence over the same option in the top-level `default.config`.

### Image Optimization

You can enable automatic image optimization by configuring the `transformations` option in the `media_libraries` configuration. This allows you to specify how uploaded images should be processed and optimized before being stored.

For example, you can convert raster images to WebP format, resize them to a maximum dimension, and optimize SVG files.

::: code-group

```yaml [YAML]{3-10}
media_libraries:
  all:
    transformations:
      raster_image: # original format
        format: webp # new format, only `webp` is supported
        quality: 85 # integer between 0 and 100, default: 85
        width: 2048 # default: original size
        height: 2048 # default: original size
      svg:
        optimize: true # default: false
```

```toml [TOML]{2-9}
[media_libraries.all]
[media_libraries.all.transformations]
[media_libraries.all.transformations.raster_image]
format = "webp"
quality = 85
width = 2048
height = 2048
[media_libraries.all.transformations.svg]
optimize = true
```

```json [JSON]{4-14}
{
  "media_libraries": {
    "all": {
      "transformations": {
        "raster_image": {
          "format": "webp",
          "quality": 85,
          "width": 2048,
          "height": 2048
        },
        "svg": {
          "optimize": true
        }
      }
    }
  }
}
```

```js [JavaScript]{4-14}
{
  media_libraries: {
    all: {
      transformations: {
        raster_image: {
          format: "webp",
          quality: 85,
          width: 2048,
          height: 2048,
        },
        svg: {
          optimize: true,
        },
      },
    },
  },
}
```

:::

Then, whenever a user selects images to upload, those images are automatically optimized, all within the browser. Raster images such as JPEG and PNG are converted to WebP format and resized if necessary. SVG images are minified using the [SVGO](https://github.com/svg/svgo) library if the `optimize` option is `true`, which removes unnecessary data such as comments and editor metadata.

In case you’re not aware, [WebP](https://developers.google.com/speed/webp) offers better compression than conventional formats and is now [widely supported](https://caniuse.com/webp) across major browsers. So there is no reason not to use WebP on the web.

* `raster_image` applies to any supported raster image format: `avif`, `gif`, `heic`, `jpeg`, `png` and `webp`. If you like, you can use a specific format as key instead of `raster_image`, or in addition to it to give one format different options.
* The `width` and `height` options are the maximum width and height in pixels, respectively. If an image is larger than the specified dimension, it will be scaled down, keeping the aspect ratio. Smaller images will not be scaled up.
* If the browser can’t encode WebP, the image may be saved in PNG format instead, with the file extension changed accordingly.
* File processing is a bit slow on Safari because [native WebP encoding](https://caniuse.com/mdn-api_htmlcanvaselement_toblob_type_parameter_webp) is [not supported](https://bugs.webkit.org/show_bug.cgi?id=183257) and the [jSquash](https://github.com/jamsinclair/jSquash) library is used instead.
* AVIF conversion is not supported because no browser has native AVIF encoding support ([Chromium won’t fix it](https://issues.chromium.org/issues/40848792)) and the third-party library (and AVIF encoding in general) is very slow.
* This feature is not intended for creating image variants in different formats and sizes. It should be done with a framework during the build process. Popular frameworks like [Astro](https://docs.astro.build/en/guides/images/), [Eleventy](https://www.11ty.dev/docs/plugins/image/), [Hugo](https://gohugo.io/content-management/image-processing/), [Next.js](https://nextjs.org/docs/pages/api-reference/components/image) and [SvelteKit](https://svelte.dev/docs/kit/images) have built-in image processing capabilities.
* Exif metadata is stripped from raster images to reduce file size. If you want to keep it, upload the original files without optimization and use the framework to process them later.

#### HEIC Photos

Photos taken on an iPhone or a recent Android phone are often saved in HEIC (HEIF) format, which only Safari can display. When the `raster_image` or `heic` transformation is configured, HEIC photos are accepted by Image fields and the Asset Library, and converted on upload like any other raster image:

* The photo is decoded within the browser using [libheif](https://github.com/strukturag/libheif) compiled to WebAssembly ([`@discourse/heic`](https://www.npmjs.com/package/@discourse/heic), a build from the [jSquash](https://github.com/jamsinclair/jSquash) project), which is downloaded from UNPKG on first use (about 300 KB). Decoding runs in a Web Worker so the interface stays responsive, and takes about half a second for a 12-megapixel photo on a recent laptop. Photos are decoded one at a time. Safari decodes HEIC natively, so nothing is downloaded there.
* A HEIC photo saved with a `.jpg` extension, which happens when a photo is renamed rather than converted, is detected by its content and converted as well. Without HEIC conversion, such a file is rejected as unusable, because browsers other than Safari can’t display it.
* A HEIC photo that can’t be decoded is rejected rather than uploaded as is.
* Use the `heic` key to give HEIC photos their own options, such as a smaller maximum dimension, since they’re typically full-resolution camera shots.
* If your site adopts a [Content Security Policy](/en/docs/security#setting-up-content-security-policy), add `blob:` to the `worker-src` directive so that the decoder can run in a Web Worker. The decoder falls back to the main thread otherwise, which freezes the interface during decoding.

Without HEIC conversion, Image fields don’t offer HEIC photos in the file picker, while `.heic` files uploaded to the Asset Library are stored as they are.

Thumbnails of HEIC photos already in the repository are generated with the same decoder regardless of the configuration.

::: info Future Plans

We may add more transformation options in the future.

:::

### File Size Limits

If you want to restrict the maximum file size for uploads, you can set the `max_file_size` option (in bytes) in the `media_libraries` configuration at the top level or in a File/Image field. The default value is `Infinity`, meaning there is no limit. The legacy field-level `media_library.config.max_file_size` option from Netlify/Decap CMS is also supported for the internal media storage.

For example, to set a maximum file size of 1 MB for all uploads, add the following to your `config.yml`:

::: code-group

```yaml [YAML]{3}
media_libraries:
  all:
    max_file_size: 1024000
```

```toml [TOML]{2}
[media_libraries.all]
max_file_size = 1024000
```

```json [JSON]{4}
{
  "media_libraries": {
    "all": {
      "max_file_size": 1024000
    }
  }
}
```

```js [JavaScript]{4}
{
  media_libraries: {
    all: {
      max_file_size: 1024000,
    },
  },
}
```

:::

### Slugification of Filenames

Some frameworks and static site generators have restrictions on filenames, such as not allowing spaces or special characters. To ensure compatibility, you can enable filename slugification by setting the `slugify_filename` option to `true` in the `media_libraries` configuration.

::: code-group

```yaml [YAML]{3}
media_libraries:
  all:
    slugify_filename: true
```

```toml [TOML]{2}
[media_libraries.all]
slugify_filename = true
```

```json [JSON]{4}
{
  "media_libraries": {
    "all": {
      "slugify_filename": true
    }
  }
}
```

```js [JavaScript]{4}
{
  media_libraries: {
    all: {
      slugify_filename: true,
    },
  },
}
```

:::

Once enabled, any uploaded file will have its filename converted to a URL-friendly format, according to the [global slug options](/en/docs/collections/entries/slugs#global-slug-options). The same applies to a file renamed in the [Asset Library](/en/docs/ui/asset-library), or in a [File](/en/docs/fields/file) or [Image](/en/docs/fields/image) field before the entry is saved: the resulting filename is shown below the input, so `Blog Photo 1.jpg` is saved as `blog-photo-1.jpg`.

### Renaming Uploaded Files

Files are uploaded with their original names by default, which are often meaningless, like `IMG_1234.jpg`, or may reveal private information. To give uploaded files consistent names, set the `filename_template` option to a template for the new filename.

::: code-group

```yaml [YAML]{3}
media_libraries:
  all:
    filename_template: '{{year}}{{month}}{{day}}-{{uuid_short}}'
```

```toml [TOML]{2}
[media_libraries.all]
filename_template = "{{year}}{{month}}{{day}}-{{uuid_short}}"
```

```json [JSON]{4}
{
  "media_libraries": {
    "all": {
      "filename_template": "{{year}}{{month}}{{day}}-{{uuid_short}}"
    }
  }
}
```

```js [JavaScript]{4}
{
  media_libraries: {
    all: {
      filename_template: "{{year}}{{month}}{{day}}-{{uuid_short}}",
    },
  },
}
```

:::

With this configuration, a photo named `IMG_1234.jpg` uploaded on September 29, 2026, would be saved as something like `20260929-392bdcf3b642.jpg`.

The template supports the same [template tags](/en/docs/collections/entries/slugs#slug-template-tags) and [string transformations](/en/docs/string-transformations) as the entry `slug` option, plus the following tags for the original file:

* `{{filename}}`: The original filename without the extension, e.g. `IMG_1234`.
* `{{extension}}`: The original file extension, e.g. `jpg`.

Keep these points in mind:

* The file extension is always appended to the new filename, so don’t include it in the template. If the file is converted to another format by [image optimization](#image-optimization), the new extension is used.
* The value of each tag is slugified according to the [global slug options](/en/docs/collections/entries/slugs#global-slug-options), while the rest of the template is used as is, except for characters that are not allowed in filenames. Enable the [`slugify_filename`](#slugification-of-filenames) option as well to slugify the whole filename.
* If a file with the same name already exists in the folder, a number is appended to the new filename, like `20260929-392bdcf3b642-1.jpg`.
* A file that replaces an existing asset keeps the name of that asset.

When a file is added to a [File](/en/docs/fields/file) or [Image](/en/docs/fields/image) field in the internal media storage, it’s renamed when the entry is saved. So the tags that refer to the entry can also be used, like `{{slug}}` for the entry slug and `{{fields.title}}` for a field value. The file is shown with the new filename in the field before the entry is saved, and the name follows any changes to the entry until then. For example, with the `{{slug}}-{{filename}}` template, a photo named `IMG_1234.jpg` added to an entry with the `summer-trip` slug would be saved as `summer-trip-img-1234.jpg`.

In a multilingual entry, the tags are filled with the content of the default locale, so a file used in several locales has the same name everywhere. If the user renames the file by hand before saving the entry, the template no longer applies to the file.

A file uploaded in the [Asset Library](/en/docs/ui/asset-library) or to an external storage provider is renamed right away, without an entry, so only the date/time tags, the unique identifier tags, `{{filename}}` and `{{extension}}` make sense there. Any other tag is replaced with a random value.

The option can also be set for a specific File or Image field, under the field’s `media_libraries` option. For example, to name the cover images of blog posts after the entry:

```yaml{7-10}
collections:
  - name: posts
    fields:
      - name: cover
        label: Cover Image
        widget: image
        media_libraries:
          default:
            config:
              filename_template: '{{slug}}-cover'
```
