Skip to content

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.

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 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 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:

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

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.

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:

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
# 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
{
  "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
{
  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 or 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.

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.

yaml
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
[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
{
  "media_libraries": {
    "all": {
      "transformations": {
        "raster_image": {
          "format": "webp",
          "quality": 85,
          "width": 2048,
          "height": 2048
        },
        "svg": {
          "optimize": true
        }
      }
    }
  }
}
js
{
  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 library if the optimize option is true, which removes unnecessary data such as comments and editor metadata.

In case you’re not aware, WebP offers better compression than conventional formats and is now widely supported 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 is not supported and the jSquash library is used instead.
  • AVIF conversion is not supported because no browser has native AVIF encoding support (Chromium won’t fix it) 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, Eleventy, Hugo, Next.js and SvelteKit 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 compiled to WebAssembly (@discourse/heic, a build from the 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, 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.

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:

yaml
media_libraries:
  all:
    max_file_size: 1024000
toml
[media_libraries.all]
max_file_size = 1024000
json
{
  "media_libraries": {
    "all": {
      "max_file_size": 1024000
    }
  }
}
js
{
  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.

yaml
media_libraries:
  all:
    slugify_filename: true
toml
[media_libraries.all]
slugify_filename = true
json
{
  "media_libraries": {
    "all": {
      "slugify_filename": true
    }
  }
}
js
{
  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. The same applies to a file renamed in the Asset Library, or in a File or 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.

yaml
media_libraries:
  all:
    filename_template: '{{year}}{{month}}{{day}}-{{uuid_short}}'
toml
[media_libraries.all]
filename_template = "{{year}}{{month}}{{day}}-{{uuid_short}}"
json
{
  "media_libraries": {
    "all": {
      "filename_template": "{{year}}{{month}}{{day}}-{{uuid_short}}"
    }
  }
}
js
{
  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 and 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, the new extension is used.
  • The value of each tag is slugified according to the 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 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 or 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 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
collections:
  - name: posts
    fields:
      - name: cover
        label: Cover Image
        widget: image
        media_libraries:
          default:
            config:
              filename_template: '{{slug}}-cover'