Internal Media Storage
The internal media storage in Sveltia CMS uses the repository’s file system to store and manage media assets such as images and files. It provides a simple and effective way to handle media uploads directly within the CMS interface. Some additional features, such as image optimization and file size limits, are also available to enhance the media management experience.
Considerations
The internal storage (Git repository) may not be suitable for a large number of media files or very large files, as it can lead to performance issues with Git operations. It’s particularly true for the GitHub backend that does not support Git LFS (Large File Storage) at this time. In such cases, consider using external storage.
Requirements
No special requirements are needed to use the internal media storage, as it works with any backend supported by Sveltia CMS.
Configuring Folder Paths
You can configure the folder paths for storing and accessing media files in the internal media storage at three levels: top-level, collection-level, and field-level. The settings at each level override the ones at the previous level.
Top-Level Configuration
Define the internal media storage settings in your config.yml file using the media_folder and public_folder options at the root level.
media_folder: /static/uploads
public_folder: /uploadsmedia_folder = "/static/uploads"
public_folder = "/uploads"{
"media_folder": "/static/uploads",
"public_folder": "/uploads"
}{
media_folder: "/static/uploads",
public_folder: "/uploads",
}Media Folder
The media_folder option specifies the folder in the repository where media files will be stored. Check your framework’s static assets handling to choose an appropriate folder.
Common static folder names
Here’s a quick reference for some popular frameworks:
| Framework / SSG | Static Folder Name |
|---|---|
| Eleventy, GitBook, Jekyll | / (root) |
| Pelican | /content |
| MkDocs, Docsify | /docs |
| Astro, Next.js, Nuxt, Remix, UmiJS, VitePress | /public |
| Hexo, Slate | /source |
| mdBook | /src |
| Docusaurus, Fresh, Gatsby, Hugo, SvelteKit, Zola | /static |
| VuePress | /.vuepress/public |
If you’re unsure about your framework’s static files folder, please refer to its official documentation.
A few notes about this option:
- It must be an absolute path relative to the root of the repository.
- Although the leading slash can be omitted, it is recommended to include it for clarity.
- To use the repository’s root folder, set this option to a slash (
/), a period (.), or an empty string ('').
If you only want to use an external media storage provider and do not need the internal media storage, you can omit the media_folder option to disable it. Otherwise, this option is required.
Note that some of the stock photo providers may still require a media_folder to function properly because they don’t allow direct linking to their CDN URLs, requiring the images to be copied to the local repository instead.
Public Folder
The public_folder option defines the public URL path that corresponds to the media_folder. The leading slash is required in this option. If public_folder is not specified, it will default to the value of media_folder.
With the above configuration, if a media file is stored in /static/uploads/image.jpg and your site is hosted at https://example.com, the public URL to access the image would be https://example.com/uploads/image.jpg.
Breaking change from Netlify/Decap CMS
Sveltia CMS does not support absolute URLs in the public_folder option. Use relative paths starting with a slash (/) instead.
Collection-Level Configuration
You can override the internal media storage settings for each collection by specifying the media_folder and public_folder options in the collection configuration.
collections:
- name: products
label: Products
folder: content/products
media_folder: /static/uploads/products
public_folder: /uploads/products[[collections]]
name = "products"
label = "Products"
folder = "content/products"
media_folder = "/static/uploads/products"
public_folder = "/uploads/products"{
"collections": [
{
"name": "products",
"label": "Products",
"folder": "content/products",
"media_folder": "/static/uploads/products",
"public_folder": "/uploads/products"
}
]
}{
collections: [
{
name: "products",
label: "Products",
folder: "content/products",
media_folder: "/static/uploads/products",
public_folder: "/uploads/products",
},
],
}If public_folder is not specified, it will default to the value of the collection-level media_folder.
Absolute vs. Relative Paths
The collection-level and field-level media_folder option must be starting with a slash (/) to indicate an absolute path from the root of the repository, while a leading slash can be omitted in the top-level media_folder option.
If you use a relative path, Sveltia CMS will treat it as relative to the collection folder (and path, if defined). See the Using entry-relative folders section below for details.
We recommend using absolute paths for better clarity and to avoid confusion, unless you specifically want to organize media files within the content folders.
Note for Netlify/Decap CMS users
The absolute path setup is not documented in the official Netlify/Decap CMS documentation, but it has been supported at least since 2020. Sveltia CMS continues to support this behavior for compatibility and better usability.
Using Placeholders
The following placeholder variables can be used in the media_folder and public_folder options, in addition to slug template tags:
{{dirname}}: The name of the directory containing the entry file, relative to the collectionfolder.{{filename}}: The entry file name without the extension. (Not the media file name.){{extension}}: The entry file extension. (Not the media file extension.){{media_folder}}: Refers to the top-levelmedia_foldersetting.{{public_folder}}: Refers to the top-levelpublic_foldersetting.
The following example is the same as the previous one, but using placeholders:
collections:
- name: products
label: Products
folder: content/products
media_folder: '{{media_folder}}/products'
public_folder: '{{public_folder}}/products'[[collections]]
name = "products"
label = "Products"
folder = "content/products"
media_folder = "{{media_folder}}/products"
public_folder = "{{public_folder}}/products"{
"collections": [
{
"name": "products",
"label": "Products",
"folder": "content/products",
"media_folder": "{{media_folder}}/products",
"public_folder": "{{public_folder}}/products"
}
]
}{
collections: [
{
name: "products",
label: "Products",
folder: "content/products",
media_folder: "{{media_folder}}/products",
public_folder: "{{public_folder}}/products",
},
],
}Using Entry-Relative Folders
Some frameworks and static site generators support organizing content and media files together in the same folder. One example is Hugo’s page bundles, where each content entry can have its own folder containing the content file and associated media files.
Assets stored in entry-relative folders are only accessible by the associated entry and not available for other entries. Therefore, Sveltia CMS automatically deletes these assets when the associated entry is deleted. When you’re working with a local repository, the empty enclosing folder is also deleted.
To configure Sveltia CMS to use entry-relative paths for media files, set the media_folder and public_folder options to empty strings ('') in your collection configuration. This tells Sveltia CMS to look for media files in the same folder as the content files.
collections:
- name: posts
label: Blog Posts
folder: /content/posts
path: '{{slug}}/index'
media_folder: ''
public_folder: ''
fields:
- { name: title, label: Title }
- { name: cover, label: Cover Image, widget: image }
- { name: body, label: Body, widget: richtext }[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
path = "{{slug}}/index"
media_folder = ""
public_folder = ""
[[collections.fields]]
name = "title"
label = "Title"
[[collections.fields]]
name = "cover"
label = "Cover Image"
widget = "image"
[[collections.fields]]
name = "body"
label = "Body"
widget = "richtext"{
"collections": [
{
"name": "posts",
"label": "Blog Posts",
"folder": "/content/posts",
"path": "{{slug}}/index",
"media_folder": "",
"public_folder": "",
"fields": [
{ "name": "title", "label": "Title" },
{ "name": "cover", "label": "Cover Image", "widget": "image" },
{ "name": "body", "label": "Body", "widget": "richtext" }
]
}
]
}{
collections: [
{
name: "posts",
label: "Blog Posts",
folder: "/content/posts",
path: "{{slug}}/index",
media_folder: "",
public_folder: "",
fields: [
{ name: "title", label: "Title" },
{ name: "cover", label: "Cover Image", widget: "image" },
{ name: "body", label: "Body", widget: "richtext" },
],
},
],
}This configuration allows you to structure your content and media files like this:
.
└─ content/
└─ posts/
└─ my-first-post/
├─ index.md
└─ image1.jpgAnd the cover image field in the index.md file will omit the folder path when referencing the image:
---
title: My First Post
cover: image1.jpg
---
Content goes here...If you want to organize media files in a subfolder within each entry folder, you can specify the subfolder name in the media_folder and public_folder options.
collections:
- name: posts
label: Blog Posts
folder: /content/posts
path: '{{slug}}/index'
media_folder: 'images'
public_folder: 'images'[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
path = "{{slug}}/index"
media_folder = "images"
public_folder = "images"{
"collections": [
{
"name": "posts",
"label": "Blog Posts",
"folder": "/content/posts",
"path": "{{slug}}/index",
"media_folder": "images",
"public_folder": "images"
}
]
}{
collections: [
{
name: "posts",
label: "Blog Posts",
folder: "/content/posts",
path: "{{slug}}/index",
media_folder: "images",
public_folder: "images",
},
],
}Then the folder structure would look like this:
.
└─ content/
└─ posts/
└─ my-first-post/
├─ index.md
└─ images/
└─ image1.jpgAnd the cover image field in the index.md file would reference the image like this:
---
title: My First Post
cover: images/image1.jpg
---
Content goes here...File-Level Configuration
Each file in a file collection can also have its own media folder settings by specifying the media_folder and public_folder options in the file configuration, which override both the top-level and collection-level settings.
collections:
- name: pages
label: Pages
files:
- name: about
label: About Page
file: content/pages/about.md
media_folder: /static/uploads/about
public_folder: /uploads/about[[collections]]
name = "pages"
label = "Pages"
[[collections.files]]
name = "about"
label = "About Page"
file = "content/pages/about.md"
media_folder = "/static/uploads/about"
public_folder = "/uploads/about"{
"collections": [
{
"name": "pages",
"label": "Pages",
"files": [
{
"name": "about",
"label": "About Page",
"file": "content/pages/about.md",
"media_folder": "/static/uploads/about",
"public_folder": "/uploads/about"
}
]
}
]
}{
collections: [
{
name: "pages",
label: "Pages",
files: [
{
name: "about",
label: "About Page",
file: "content/pages/about.md",
media_folder: "/static/uploads/about",
public_folder: "/uploads/about",
},
],
},
],
}The same placeholder variables mentioned above can be used in field-level media_folder and public_folder options.
Field-Level Configuration
You can also configure media storage settings for individual File or Image fields within a collection. This allows you to specify different media folders for different fields, overriding both the top-level and collection-level settings.
fields:
- name: thumbnail
label: Thumbnail Image
widget: image
media_folder: /static/uploads/thumbnails
public_folder: /uploads/thumbnails[[fields]]
name = "thumbnail"
label = "Thumbnail Image"
widget = "image"
media_folder = "/static/uploads/thumbnails"
public_folder = "/uploads/thumbnails"{
"fields": [
{
"name": "thumbnail",
"label": "Thumbnail Image",
"widget": "image",
"media_folder": "/static/uploads/thumbnails",
"public_folder": "/uploads/thumbnails"
}
]
}{
fields: [
{
name: "thumbnail",
label: "Thumbnail Image",
widget: "image",
media_folder: "/static/uploads/thumbnails",
public_folder: "/uploads/thumbnails",
},
],
}The same placeholder variables mentioned above can be used in field-level media_folder and public_folder options.
Field-level media_folder and public_folder options can also be set to empty strings ('') or subfolder names to use entry-relative paths, just like in the collection-level configuration.
Additional Features
There are several additional features available in the internal media storage to enhance your media management experience.
Image Optimization
Ever wanted to prevent end-users from adding huge images to your repository? The built-in image optimizer in Sveltia CMS makes developers’ lives easier with a simple configuration like this:
media_libraries:
default:
config:
transformations:
raster_image: # original format
format: webp # new format, only `webp` is supported
quality: 85 # default: 85
width: 2048 # default: original size
height: 2048 # default: original size
svg:
optimize: true[media_libraries.default]
[media_libraries.default.config]
[media_libraries.default.config.transformations]
[media_libraries.default.config.transformations.raster_image]
format = "webp"
quality = 85
width = 2048
height = 2048
[media_libraries.default.config.transformations.svg]
optimize = true{
"media_libraries": {
"default": {
"config": {
"transformations": {
"raster_image": {
"format": "webp",
"quality": 85,
"width": 2048,
"height": 2048
},
"svg": {
"optimize": true
}
}
}
}
}
}{
media_libraries: {
default: {
config: {
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.
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,bmp,gif,jpeg,pngandwebp. If you like, you can use a specific format as key instead ofraster_image.- The
widthandheightoptions are the maximum width and height, respectively. If an image is larger than the specified dimension, it will be scaled down. Smaller images will not be resized. - 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.
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 in the internal media storage, you can set the max_file_size option (in bytes) in the media_libraries configuration at the top level, collection level, or field level. The default value is Infinity, meaning there is no limit.
For example, to set a maximum file size of 1 MB for all uploads in the internal media storage, add the following to your config.yml:
media_libraries:
default:
config:
max_file_size: 1024000[media_libraries.default]
[media_libraries.default.config]
max_file_size = 1024000{
"media_libraries": {
"default": {
"config": {
"max_file_size": 1024000
}
}
}
}{
media_libraries: {
default: {
config: {
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 in the internal media storage by setting the slugify_filename option to true in the media_libraries configuration.
media_libraries:
default:
config:
slugify_filename: true[media_libraries.default]
[media_libraries.default.config]
slugify_filename = true{
"media_libraries": {
"default": {
"config": {
"slugify_filename": true
}
}
}
}{
media_libraries: {
default: {
config: {
slugify_filename: true,
},
},
},
}Once enabled, any uploaded file will have its filename converted to a URL-friendly format, according to the global slug options.
Accessing the Storage
There are two main ways to use the internal media storage in Sveltia CMS:
File and Image Fields
When editing content entries, you can use File and Image fields to upload and select media assets directly within the entry editor. Click the Browse button to open the media picker, where you can select existing assets or upload new ones. These fields also support drag-and-drop functionality for easy uploads.
Standalone Asset Library
You can access the Asset Library from the main navigation menu in the CMS interface. Here, you can view, upload, and manage all your media assets in one place. You can view assets in a grid or list format, search for specific files, and view asset details such as file size, dimensions and a list of entries using the asset.