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:
- Amazon S3 and S3-compatible providers:
- Backblaze B2
- Bunny Storage
- Cloudflare R2
- DigitalOcean Spaces
- Scaleway Object Storage
- Supabase Storage
- Any other S3-compatible service, including self-hosted servers such as Garage and MinIO, through the Amazon S3 integration’s
endpointoption. See Self-Hosted Storage for details.
- Azure Blob Storage
- Cloudinary
- Uploadcare
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:
# 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/'# 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/"{
"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/"
}
}
}
}{
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:
media_library:
name: cloudinary
config:
cloud_name: YOUR_CLOUD_NAME
api_key: YOUR_API_KEY
output_filename_only: trueAdditional 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.
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[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{
"media_libraries": {
"all": {
"transformations": {
"raster_image": {
"format": "webp",
"quality": 85,
"width": 2048,
"height": 2048
},
"svg": {
"optimize": true
}
}
}
}
}{
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_imageapplies to any supported raster image format:avif,gif,heic,jpeg,pngandwebp. If you like, you can use a specific format as key instead ofraster_image, or in addition to it to give one format different options.- The
widthandheightoptions 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
.jpgextension, 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
heickey 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 theworker-srcdirective 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:
media_libraries:
all:
max_file_size: 1024000[media_libraries.all]
max_file_size = 1024000{
"media_libraries": {
"all": {
"max_file_size": 1024000
}
}
}{
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.
media_libraries:
all:
slugify_filename: true[media_libraries.all]
slugify_filename = true{
"media_libraries": {
"all": {
"slugify_filename": true
}
}
}{
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.
media_libraries:
all:
filename_template: '{{year}}{{month}}{{day}}-{{uuid_short}}'[media_libraries.all]
filename_template = "{{year}}{{month}}{{day}}-{{uuid_short}}"{
"media_libraries": {
"all": {
"filename_template": "{{year}}{{month}}{{day}}-{{uuid_short}}"
}
}
}{
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_filenameoption 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:
collections:
- name: posts
fields:
- name: cover
label: Cover Image
widget: image
media_libraries:
default:
config:
filename_template: '{{slug}}-cover'