Nested Entries
With the nested and meta options, you can organize content that has a hierarchical relationship, such as categories and subcategories, and allow editors to create nested entries easily.
Note for Netlify/Decap CMS users
In Sveltia CMS, what was previously referred to as a nested collection in Netlify/Decap CMS is now called nested entries. This change was made to better reflect that the entries are nested in a folder tree within a single collection, rather than collections nested in each other.
Sveltia CMS also fixes a number of long-standing problems with this feature, which remains in beta in Netlify/Decap CMS. These include entry paths, preview paths, media folders, folder labels and i18n support. See Nested entry enhancements for the details.
The nested option turns the collection into a folder tree in the sidebar and lets entries live in subfolders of any depth. The meta.path option adds a Path field to the Content Editor, so editors can choose where a new entry goes and move an existing one later.
collections:
- name: pages
label: Pages
label_singular: Page
folder: /content/pages
nested:
depth: 100
summary: '{{title}}'
subfolders: true
meta: { path: { index_file: _index } }
fields:
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }[[collections]]
name = "pages"
label = "Pages"
label_singular = "Page"
folder = "/content/pages"
[collections.nested]
depth = 100
summary = "{{title}}"
subfolders = true
[collections.meta.path]
index_file = "_index"
[[collections.fields]]
name = "title"
label = "Title"
[[collections.fields]]
name = "body"
label = "Body"
widget = "markdown"{
"collections": [
{
"name": "pages",
"label": "Pages",
"label_singular": "Page",
"folder": "/content/pages",
"nested": {
"depth": 100,
"summary": "{{title}}",
"subfolders": true
},
"meta": {
"path": {
"index_file": "_index"
}
},
"fields": [
{ "name": "title", "label": "Title" },
{ "name": "body", "label": "Body", "widget": "markdown" }
]
}
]
}{
collections: [
{
name: "pages",
label: "Pages",
label_singular: "Page",
folder: "/content/pages",
nested: {
depth: 100,
summary: "{{title}}",
subfolders: true,
},
meta: {
path: {
index_file: "_index",
},
},
fields: [
{ name: "title", label: "Title" },
{ name: "body", label: "Body", widget: "markdown" },
],
},
],
}Nested Entry Options
The nested option accepts the following properties:
depth: The maximum number of path segments below the collectionfolder, counting the filename itself. A file stored deeper than this is not part of the collection. The default is unlimited.- In the default
subfoldersmode, where the last segment is always the index file,depth: 3allows up to two folder levels, such asproducts/hardware/_index.md. - The segments of a
pathtemplate are part of the count, too.
- In the default
summary: A summary template used to label the folders in the tree. It overrides the collection’s ownsummaryoption, which continues to be used in the entry list. The default is the collection’ssummaryoption value.subfolders: Whether each entry is stored as an index file in its own folder. The default istrue. See below for what changes when it’sfalse.
Storing Nested Entries
In the default subfolders mode, a folder is an entry: each entry is stored as an index file, and the folders below it are its children. This suits Hugo and Zola, which both make a folder a section by putting an _index.md file in it, and any other generator that gives a folder a page of its own:
.
└─ content/
└─ pages/
├─ _index.md # Home
└─ products/
├─ _index.md # Products
├─ hardware/
│ └─ _index.md # Hardware
└─ software/
└─ _index.md # SoftwareWith subfolders: false, entries are regular files that keep their own names, and folders are just folders. This suits Docusaurus, VitePress, Starlight, MkDocs, as well as the pages of an Eleventy or Jekyll site, and similar setups, where every file becomes a page at its own path:
.
└─ content/
└─ pages/
├─ overview.md # Overview
└─ products/
├─ hardware.md # Hardware
└─ software.md # SoftwareThe mode determines which entries are listed when a folder is selected:
- In the
subfoldersmode, the list shows the entries in the immediate subfolders, plus the collection’s own index file at the root. In the tree above,ProductslistsHardwareandSoftware, and the collection root listsHomeandProducts. - Otherwise, the list shows the files stored directly in the selected folder. In the tree above,
productslistsHardwareandSoftware, while the root listsOverview.
A nested entry is identified by where it sits rather than by a name of its own. Its slug is therefore its path below the collection folder — products/hardware for the tree above.
Wherever that slug is used to refer to the entry, the filename shared by every entry is left out. This applies to a preview path and to the value a Relation field stores. The collection’s own index file keeps its name, because there would be nothing left of it otherwise.
The root index file is only picked up when it fits the collection’s file paths. With a path option such as {{slug}}/_index, a bare content/pages/_index.md has no slug folder in front of it, so it’s not part of the collection. To bring it in, use the index_file collection option, which also lets it have fields of its own.
The mode also decides what happens when an entry is filed elsewhere with the path editor. In the subfolders mode, the entry’s whole folder moves, taking its children along. Otherwise, only the entry’s own file moves, and it keeps its name.
Nesting Page Bundles
Because the subfolders mode gives each entry a folder of its own, a relative media_folder is enough to turn the collection into a tree of page bundles, where each entry keeps its media beside its index file. Adding the path option on top restricts the collection to those index files, so that other files stored in the same folders are left alone:
collections:
- name: pages
label: Pages
label_singular: Page
folder: /content/pages
path: '{{slug}}/_index'
media_folder: ''
public_folder: ''
nested:
depth: 100
summary: '{{title}}'
subfolders: true
meta: { path: { index_file: _index } }
fields:
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }
- { name: image, label: Image, widget: image }[[collections]]
name = "pages"
label = "Pages"
label_singular = "Page"
folder = "/content/pages"
path = "{{slug}}/_index"
media_folder = ""
public_folder = ""
[collections.nested]
depth = 100
summary = "{{title}}"
subfolders = true
[collections.meta.path]
index_file = "_index"
[[collections.fields]]
name = "title"
label = "Title"
[[collections.fields]]
name = "body"
label = "Body"
widget = "markdown"
[[collections.fields]]
name = "image"
label = "Image"
widget = "image"{
"collections": [
{
"name": "pages",
"label": "Pages",
"label_singular": "Page",
"folder": "/content/pages",
"path": "{{slug}}/_index",
"media_folder": "",
"public_folder": "",
"nested": {
"depth": 100,
"summary": "{{title}}",
"subfolders": true
},
"meta": {
"path": {
"index_file": "_index"
}
},
"fields": [
{ "name": "title", "label": "Title" },
{ "name": "body", "label": "Body", "widget": "markdown" },
{ "name": "image", "label": "Image", "widget": "image" }
]
}
]
}{
collections: [
{
name: "pages",
label: "Pages",
label_singular: "Page",
folder: "/content/pages",
path: "{{slug}}/_index",
media_folder: "",
public_folder: "",
nested: {
depth: 100,
summary: "{{title}}",
subfolders: true,
},
meta: {
path: {
index_file: "_index",
},
},
fields: [
{ name: "title", label: "Title" },
{ name: "body", label: "Body", widget: "markdown" },
{ name: "image", label: "Image", widget: "image" },
],
},
],
}The path option is optional here. For nested entries, it says where an entry sits within the folder holding it, rather than within the collection folder. It therefore takes the same form as in a flat collection, and its last segment — the filename — has to match meta.path.index_file.
If you leave it out, the collection takes in every file below its folder. If you set it, only the index files are entries. That’s what you want if the same folders hold other Markdown that isn’t a page.
Either way, entries can be nested to any depth, each with media of its own:
.
└─ content/
└─ pages/
└─ about/
├─ _index.md # About
├─ portrait.jpg
└─ team/
├─ _index.md # Team
└─ group-photo.jpgThe depth option counts path segments the same way as it does without path, with the template’s own segments included in the count. Because {{slug}}/_index takes two of them, depth: 3 allows one more folder level, such as about/team/_index.md.
Browsing Nested Entries
The collection appears in the sidebar as a tree. Selecting a folder lists its entries in the main area, and the URL reflects the folder being browsed, so a link to a specific folder can be shared:
https://YOUR_DOMAIN/admin/#/collections/COLLECTION_NAME/filter/FOLDER_PATHSuch a link keeps working for as long as the folder does. A folder exists while it holds an entry, directly or further down. A folder that has been emptied — or that never existed — shows a Not Found page rather than an empty list. So does a folder path on a collection without the nested option.
A folder that still holds an entry but has nothing to list, such as a page with no children, shows an empty list as usual.
In the subfolders mode, a folder that has no subfolder of its own is left out of the tree, because such a folder is an entry rather than a container — it’s already listed in its parent folder’s entry list. Set subfolders: false if you want every folder to appear in the tree.
Each folder in the tree is labeled with the summary of its index file, falling back to the folder name. With subfolders: false, the folder name is always used.
Choosing a Parent Folder
The meta.path option adds a Parent Folder field above the other fields in the Content Editor. It shows the folder the entry is filed in and opens a folder tree for choosing a different one. The option accepts the following properties:
index_file: The filename, without an extension, that every entry in the collection is saved as, such as_indexorindex. Thesubfoldersmode needs it, because a folder’s own entry has to have a fixed name. Without it, each entry is named after its slug and stored as a regular file in the chosen folder. It has no effect withsubfolders: false, where entries always keep their own names.widgetandlabel: Accepted for compatibility with Netlify/Decap CMS but ignored. The field is always a folder picker.
Note for Netlify/Decap CMS users
Netlify offered an experimental parent widget that allowed users to select a parent folder from a dropdown list rather than a string field. However, this feature was never integrated into Netlify CMS itself and is not compatible with Decap CMS. Sveltia CMS has its own built-in folder picker to improve the user experience, so the widget option is ignored. The label option is also ignored because the field is always labeled “Parent Folder”.
index_file vs meta.path.index_file
These two options look similar but do different things. The index_file collection option includes one special file, Hugo’s _index.md, in a regular entry collection and lets it have its own set of fields. The meta.path.index_file option names every entry in a collection with nested entries. You can use both in the same collection if the root index file needs different fields than the section pages.
The option has no effect on its own: it needs nested, because without a hierarchy there is no folder to choose.
Creating Entries in a Folder
When an editor creates an entry while browsing a folder, the field starts on that folder, so the new entry is filed alongside the ones already listed. The tree lists every folder in the collection, and the folder an entry occupies is left out of its own picker so it can’t be filed within itself.
In the subfolders mode, a new entry gets a folder of its own within the chosen one, named after its slug. Creating “Release Notes” while browsing docs/guides therefore stores it at docs/guides/release-notes/_index.md. Elsewhere, the entry is a regular file named after its slug, so the same page becomes docs/guides/release-notes.md.
Creating Folders
With subfolders: false, creating an entry never creates a folder, so the tree would only ever show folders that already contain a file. The New Folder button below the tree fills that gap. Pick the parent folder, enter a name, and the new folder becomes the parent of the entry you’re creating.
The name is normalized like an entry slug, so “User Guides” becomes user-guides. It’s rejected if it:
- contains a slash
- starts with a dot, which would hide the folder
- has no letters or numbers left after normalization
- is already used by another folder in the same parent
The folder reaches the repository only when you save an entry into it. Git tracks files, not folders, so it can’t commit an empty folder.
The button isn’t shown in the subfolders mode, because creating an entry there already creates its folder.
Moving Entries
Choosing a different folder for an existing entry moves its file, keeping the name it already has. The Save button is enabled by the folder change alone, so an entry can be moved without touching its content. The CMS rejects a folder that is already taken by another entry.
In the subfolders mode, everything below the entry’s folder moves with it in the same commit: its child entries, as well as any entry-relative media stored alongside them. A whole section can therefore be reorganized in one save.
To change the entry’s name instead of the folder it sits in, use the Slug panel. It renames the entry’s own folder and takes everything below it along the same way.