Nested Collections
With the nested and meta options, you can organize contents that have a hierarchical relationship, such as categories and subcategories, and allow editors to create nested entries easily. This feature is called nested collections in Netlify/Decap CMS.
Note for Netlify/Decap CMS users
Sveltia CMS 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 collection 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 Collection Options
The nested option accepts the following properties:
depth: The maximum number of path segments below the collectionfolder, counting the file name 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 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 subfolders mode, the list shows the entries in the immediate subfolders, plus the collection’s own index file at the root — Products in the tree above lists Hardware and Software, and the collection root lists Home and Products. Otherwise, the list shows the files stored directly in the selected folder, so products lists Hardware and Software while the root lists Overview.
An entry in a nested collection is identified by where it sits rather than by a name of its own, so its slug is its path below the collection folder — products/hardware for the tree above. Wherever that slug is used to refer to the entry, the file name shared by every entry is left out: in a preview path, and in the value a Relation field stores. The collection’s own index file keeps its name, because there would be nothing left of it.
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 and is therefore not part of the collection; use the index_file collection option to bring it in, 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, while 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. In a nested collection it says where an entry sits within the folder holding it, rather than within the collection folder, so it takes the same form as in a flat collection, and its last segment — the file name — has to match meta.path.index_file. Leave it out and the collection takes in every file below its folder; set it and only the index files are entries, which is 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 you’re browsing, 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, so one that has been emptied — or that never existed — shows a Not Found page rather than an empty list, and 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 labelled 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 file name, 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”.
The option has no effect on its own: it needs nested, because without a hierarchy there is no folder to choose.
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.
With subfolders: false, creating an entry never creates a folder, so the tree could otherwise only ever show the folders that already hold a file. A New Folder button below the tree fills that gap: pick the folder to create it in, give it a name, and the new folder becomes the entry’s parent. The name is normalized like an entry slug — “User Guides” becomes user-guides — and is rejected if it contains a slash, starts with a dot, which would hide the folder, keeps no letter or number once normalized, or is already used by a folder in the same parent. The folder itself reaches the repository when the entry is saved into it, because Git tracks files rather than folders and so has no way to commit an empty folder. The button isn’t shown in the subfolders mode, where creating an entry already creates the folder that holds it.
Choosing a different folder for an existing entry moves its file, keeping the name it already has. To change that name instead of the folder it sits in, use the Slug Editor, which renames the entry’s own folder and takes everything below it along the same way. The Save button is enabled by the folder alone, so an entry can be moved without touching its content. 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 — so a whole section can be reorganized in one save. The CMS rejects a folder that is already taken by another entry.
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 nested collection. You can use both in the same collection if the root index file needs different fields than the section pages.