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: /public/uploads
public_folder: /uploadsmedia_folder = "/public/uploads"
public_folder = "/uploads"{
"media_folder": "/public/uploads",
"public_folder": "/uploads"
}{
media_folder: "/public/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 ('').
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 /public/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.
Disabling Internal Media Storage
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.
You can also disable the internal media storage for specific fields.
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: /public/uploads/products
public_folder: /uploads/products[[collections]]
name = "products"
label = "Products"
folder = "content/products"
media_folder = "/public/uploads/products"
public_folder = "/uploads/products"{
"collections": [
{
"name": "products",
"label": "Products",
"folder": "content/products",
"media_folder": "/public/uploads/products",
"public_folder": "/uploads/products"
}
]
}{
collections: [
{
name: "products",
label: "Products",
folder: "content/products",
media_folder: "/public/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: /public/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 = "/public/uploads/about"
public_folder = "/uploads/about"{
"collections": [
{
"name": "pages",
"label": "Pages",
"files": [
{
"name": "about",
"label": "About Page",
"file": "content/pages/about.md",
"media_folder": "/public/uploads/about",
"public_folder": "/uploads/about"
}
]
}
]
}{
collections: [
{
name: "pages",
label: "Pages",
files: [
{
name: "about",
label: "About Page",
file: "content/pages/about.md",
media_folder: "/public/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: /public/uploads/thumbnails
public_folder: /uploads/thumbnails[[fields]]
name = "thumbnail"
label = "Thumbnail Image"
widget = "image"
media_folder = "/public/uploads/thumbnails"
public_folder = "/uploads/thumbnails"{
"fields": [
{
"name": "thumbnail",
"label": "Thumbnail Image",
"widget": "image",
"media_folder": "/public/uploads/thumbnails",
"public_folder": "/uploads/thumbnails"
}
]
}{
fields: [
{
name: "thumbnail",
label: "Thumbnail Image",
widget: "image",
media_folder: "/public/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.
Disabling Internal Media Storage for a Field
If you have enabled an external media storage provider and want to disable the internal media storage for a specific field, you can add the media_libraries option with the default library set to false in the field configuration. This will prevent the media picker from showing the internal media library and only allow selecting from the external provider.
fields:
- name: cover
label: Cover Image
widget: image
media_libraries:
default: false[[fields]]
name = "cover"
label = "Cover Image"
widget = "image"
[fields.media_libraries]
default = false{
"fields": [
{
"name": "cover",
"label": "Cover Image",
"widget": "image",
"media_libraries": {
"default": false
}
}
]
}{
fields: [
{
name: "cover",
label: "Cover Image",
widget: "image",
media_libraries: {
default: false,
},
},
],
}Additional Features
For backward compatibility, the additional media storage features can be configured specifically for the internal media storage. This configuration goes in the media_libraries option, under the default → config key, which applies only to the internal media storage provider, as opposed to the all key that applies to all providers.
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,
},
},
},
},
},
}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,
},
},
},
}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,
},
},
},
}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.