---
url: /en/docs/collections/entries/slugs.md
description: >-
  Customize entry slugs and file paths in a Sveltia CMS entry collection using
  template tags, identifier fields, subfolders and page bundles.
---

# Slugs and File Paths

An entry’s slug is its filename, and the file path is where that file is stored under the collection’s `folder`. Both can be customized with the options described on this page.

## Entry Slugs

Sveltia CMS provides several ways to customize the slug (filename) of an entry in a collection.

### Global Slug Options

The `slug` option defined at the top-level of the configuration file applies to all collections. The default settings are as follows:

::: code-group

```yaml [YAML]
slug:
  encoding: unicode
  clean_accents: false
  sanitize_replacement: '-'
  trim: true
  lowercase: true
  timezone: utc
```

```toml [TOML]
[slug]
encoding = "unicode"
clean_accents = false
sanitize_replacement = "-"
trim = true
lowercase = true
timezone = "utc"
```

```json [JSON]
{
  "slug": {
    "encoding": "unicode",
    "clean_accents": false,
    "sanitize_replacement": "-",
    "trim": true,
    "lowercase": true,
    "timezone": "utc"
  }
}
```

```js [JavaScript]
{
  slug: {
    encoding: "unicode",
    clean_accents: false,
    sanitize_replacement: "-",
    trim: true,
    lowercase: true,
    timezone: "utc",
  },
}
```

:::

The available options are:

* `encoding`: Specifies the encoding method for slugs. Supported values are `unicode` (default) and `ascii`.
  * `unicode`: Allows Unicode characters in slugs, preserving non-Latin scripts.
  * `ascii`: Sanitizes slugs to ASCII characters only. The allowed characters are 0-9, a-z, A-Z, hyphen (`-`) underscore (`_`) and tilde (`~`). Other characters are replaced with the value specified in the `sanitize_replacement` option.
* `clean_accents`: A boolean value indicating whether to remove accents from characters in slugs. If enabled, accented characters are converted to their unaccented equivalents (e.g., `é` becomes `e`). Also, certain characters like German umlauts are [transliterated](https://en.wikipedia.org/wiki/Transliteration) to their ASCII equivalents (e.g., `ß` becomes `ss`). The default value is `false`.
  * See the `transliterate` library’s [replacements list](https://github.com/sindresorhus/transliterate/blob/main/replacements.js) for details on how specific characters are transliterated. [Additional rules](https://github.com/sindresorhus/transliterate/blob/main/locale-replacements.js) are applied when [i18n support](/en/docs/i18n) is enabled for [specific locales](https://github.com/sindresorhus/transliterate#supported-locales).
* `sanitize_replacement`: A string used to substitute invalid characters. The default value is a hyphen (`-`).
* `maxlength`: An integer specifying the maximum length of the slug. If the generated slug exceeds this length, it will be truncated. This is useful for CI/CD services or filesystems that impose filename length restrictions. The default value is `undefined`, meaning there is no length limit.
* `trim`: A boolean value indicating whether to trim leading and trailing `sanitize_replacement` characters from the slug. The default value is `true`.
* `lowercase`: A boolean value indicating whether to convert the slug to lowercase. The default value is `true`. Changing this to `false` will preserve the original casing of the title or identifier field.
* `timezone`: A string specifying the timezone to use when generating date-based slugs with [template tags](#slug-template-tags) like `{{day}}` and `{{hour}}`. The default value is `utc`. You can set this to `local` to use the local timezone of the user.

::: warning Deprecation Notice

The collection-level `slug_length` option has been deprecated in favor of the `maxlength` global slug option described above. The `slug_length` option will be removed in Sveltia CMS v1.0.0. If you are upgrading from an older version, update your configuration accordingly.

:::

### How Slugs are Generated

By default, Sveltia CMS uses the `title` field as the slug (filename) for entries in a collection.

If a collection only has the Markdown `body` field, an entry slug will be generated from a header in the `body`, if exists. This aims to support a typical [VitePress](/en/docs/frameworks/vitepress) or [Docusaurus](/en/docs/frameworks/docusaurus) setup. If no title or header is found, a part of a random UUID will be used to ensure uniqueness.

### Specifying an Identifier Field

If you want to use a different field as the entry identifier for generating slugs and filenames, you can specify it using the `identifier_field` option in the collection definition. This is useful when your entries have a unique identifier field other than `title`, such as `name` or `id`. For example, to use a `product_name` field as the identifier:

::: code-group

```yaml [YAML]{5}
collections:
  - name: products
    label: Products
    folder: /content/products
    identifier_field: product_name
    fields:
      - { name: product_name, label: Product Name }
      - { name: description, label: Description, widget: richtext }
```

```toml [TOML]{5}
[[collections]]
name = "products"
label = "Products"
folder = "/content/products"
identifier_field = "product_name"

[[collections.fields]]
name = "product_name"
label = "Product Name"

[[collections.fields]]
name = "description"
label = "Description"
widget = "richtext"
```

```json [JSON]{7}
{
  "collections": [
    {
      "name": "products",
      "label": "Products",
      "folder": "/content/products",
      "identifier_field": "product_name",
      "fields": [
        { "name": "product_name", "label": "Product Name" },
        { "name": "description", "label": "Description", "widget": "richtext" }
      ]
    }
  ]
}
```

```js [JavaScript]{7}
{
  collections: [
    {
      name: "products",
      label: "Products",
      folder: "/content/products",
      identifier_field: "product_name",
      fields: [
        { name: "product_name", label: "Product Name" },
        { name: "description", label: "Description", widget: "richtext" },
      ],
    },
  ],
}
```

:::

### Defining Entry Slugs

The `slug` option allows you to define a custom template for generating entry slugs using various [template tags](#slug-template-tags) and field names.

For example, to create slugs that include the year and month of creation along with the entry slug, you can use the following configuration:

::: code-group

```yaml [YAML]{5}
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    slug: '{{year}}-{{month}}-{{slug}}'
```

```toml [TOML]{5}
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
slug = "{{year}}-{{month}}-{{slug}}"
```

```json [JSON]{7}
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "slug": "{{year}}-{{month}}-{{slug}}"
    }
  ]
}
```

```js [JavaScript]{7}
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      slug: "{{year}}-{{month}}-{{slug}}",
    },
  ],
}
```

:::

Any field name defined in the collection’s `fields` option can be used as a template tag in the `slug` option. For example, if you have a `date` field in the collection, you can use `{{date}}` in the `slug` option to include the date in the slug. For nested fields, use dot notation, e.g. `{{author.name}}`.

If a field’s name matches one of the [predefined template tags](#slug-template-tags), such as `slug`, `year` or `uuid`, you need to prefix it with `fields.`, like `{{fields.slug}}`, to avoid confusion with the tag itself.

You can use [string transformations](/en/docs/string-transformations) with these template tags as well. For example, to create slugs that include the full date in `YYYY-MM-DD` format along with a custom `slug` field, you can use the following configuration:

::: code-group

```yaml [YAML]{5}
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    slug: "{{date | date('YYYY-MM-DD')}}-{{fields.slug}}"
```

```toml [TOML]{5}
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
slug = "{{date | date('YYYY-MM-DD')}}-{{fields.slug}}"
```

```json [JSON]{7}
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "slug": "{{date | date('YYYY-MM-DD')}}-{{fields.slug}}"
    }
  ]
}
```

```js [JavaScript]{7}
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      slug: "{{date | date('YYYY-MM-DD')}}-{{fields.slug}}",
    },
  ],
}
```

:::

::: tip

The `slug` option value should not contain slashes (`/`). If you need to create a nested folder structure for entries, use the [`path` option](#using-subfolders) instead.

:::

### Slug Template Tags

The following template tags are supported in the `slug` option:

* `{{slug}}`: The slugified version of the entry’s `title` field (or the field defined with the [`identifier_field` option](#specifying-an-identifier-field)).
* `{{year}}`: 4-digit year of the entry creation date.
* `{{month}}`: 2-digit month of the entry creation date.
* `{{day}}`: 2-digit day of the entry creation date.
* `{{hour}}`: 2-digit hour of the entry creation date.
* `{{minute}}`: 2-digit minute of the entry creation date.
* `{{second}}`: 2-digit second of the entry creation date.

By default, the entry creation date is based on the UTC timezone for backward compatibility with Netlify/Decap CMS. To use the local timezone of the user instead, set the `timezone` option to `local` in the [global slug options](#global-slug-options).

Additionally, the following unique identifier tags are available. These tags generate random values for each entry, ensuring uniqueness. This is particularly useful when the entry title may change later or when the title contains characters that are not suitable for filenames, such as non-Latin scripts.

* `{{uuid}}`: A random UUID v4, e.g. `4fc0917c-8aea-4ad5-a476-392bdcf3b642`
* `{{uuid_short}}`: The last 12 characters of a random UUID v4, e.g. `392bdcf3b642`.
* `{{uuid_shorter}}`: The first 8 characters of a random UUID v4, e.g. `4fc0917c`.

### Making Slugs Editable

By default, an entry’s slug is generated from the `title` field or the template defined in the `slug` option, and users never see it while writing. To have users choose the slug themselves instead, set the `slug` option to the special `{{fields._slug}}` tag.

With that option in place, a required Slug field appears above the other fields in the Edit Pane. It looks like a standard string field, but its value becomes the entry slug. The field starts out empty, so the entry can’t be saved until a slug has been entered, and slashes and whitespace are rejected.

::: info Only while creating an entry

The Slug field is shown only while an entry is being created, including when an existing entry is duplicated. Once the entry has been saved, the field disappears.

A saved entry can still be renamed, but only with the [Slug Editor](/en/docs/ui/content-editor#slug-editor) in the 3-dot menu of the Content Editor. Renaming moves the entry’s file and rewrites every reference to it, so it’s deliberately kept out of the Edit Pane, where it could otherwise be changed by accident in the middle of routine editing.

:::

In an [i18n](/en/docs/i18n)-enabled collection, `{{fields._slug}}` makes the slug editable in the default locale only, and the remaining locales show the same value as read-only. To let users enter a different slug for each locale, set the `slug` option to `{{fields._slug | localize}}` instead.

## File Paths

Sveltia CMS provides a couple of options to customize the file paths of entries in a collection.

### Using Subfolders

By default, Sveltia CMS saves entries directly under the specified `folder` using the slug as the filename. However, you can organize entries into subfolders using the `path` option.

Just like the [`slug` option](#defining-entry-slugs) described above, the `path` option can use template tags to create dynamic folder structures. The [slug template tags](#slug-template-tags) and [string transformations](/en/docs/string-transformations) can be used in the `path` option, along with any field names defined in the collection’s `fields` option. For nested fields, use dot notation, e.g. `{{author.name}}`.

For example, to save blog posts in subfolders based on the year and month of creation, you can use the following configuration:

::: code-group

```yaml [YAML]{5}
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    path: '{{year}}/{{month}}/{{slug}}'
```

```toml [TOML]{5}
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
path = "{{year}}/{{month}}/{{slug}}"
```

```json [JSON]{7}
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "path": "{{year}}/{{month}}/{{slug}}"
    }
  ]
}
```

```js [JavaScript]{7}
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      path: "{{year}}/{{month}}/{{slug}}",
    },
  ],
}
```

:::

With the above configuration, a blog post created on June 15, 2025, with the title “My First Post” will be saved at `content/posts/2025/06/my-first-post.md`.

#### Creating Page Bundles

You can create nested structures like Hugo’s [page bundles](https://gohugo.io/content-management/page-bundles/), or Zola’s [asset colocation](https://www.getzola.org/documentation/content/overview/#asset-colocation), which uses the same `index.md` convention, using the `path`, [`media_folder` and `public_folder` options](/en/docs/media/internal#using-entry-relative-folders) together. For example, to create a leaf bundle for each blog post, you can use the following configuration:

::: code-group

```yaml [YAML]{5-7}
collections:
  - name: posts
    label: Blog Posts
    folder: /content/posts
    path: '{{slug}}/index'
    media_folder: ''
    public_folder: ''
```

```toml [TOML]{5-7}
[[collections]]
name = "posts"
label = "Blog Posts"
folder = "/content/posts"
path = "{{slug}}/index"
media_folder = ""
public_folder = ""
```

```json [JSON]{7-9}
{
  "collections": [
    {
      "name": "posts",
      "label": "Blog Posts",
      "folder": "/content/posts",
      "path": "{{slug}}/index",
      "media_folder": "",
      "public_folder": ""
    }
  ]
}
```

```js [JavaScript]{7-9}
{
  collections: [
    {
      name: "posts",
      label: "Blog Posts",
      folder: "/content/posts",
      path: "{{slug}}/index",
      media_folder: "",
      public_folder: "",
    },
  ],
}
```

:::

With the above configuration, a blog post with the title “My First Post” will be saved at `content/posts/my-first-post/index.md`, and its media files will be stored in the same folder.

The same options can be combined with the `nested` option to build a tree of page bundles, where each entry has both its own media folder and its own child entries. See [Nesting Page Bundles](/en/docs/collections/entries/nested#nesting-page-bundles).

### Constructing File Paths

A folder collection’s file path is determined by multiple factors: the `i18n`, `folder`, `path`, `slug` and `extension` options. The configuration can be complex, especially with i18n support, so let’s break it down.

* The [`i18n`](/en/docs/i18n) global or collection option (optional)
  * It can be configured to add internationalization (i18n) support to your site.
  * The `structure` and `omit_default_locale_from_file_path` options affect the entry file path.
* The `folder` collection option (required)
  * It specifies the folder where the collection entries are stored, relative to the repository’s root directory.
  * It can contain slashes to create a nested folder structure.
* The [`path`](#using-subfolders) collection option (optional)
  * It defaults to `{{slug}}`, which is the `slug` collection option value.
  * It can contain template tags.
  * It can also contain slashes to create a nested folder structure.
* The [`slug`](#entry-slugs) collection option (optional)
  * It defaults to `{{title}}`, which is the entry’s `title` field value’s slugified version.
  * It can contain template tags but *cannot* contain slashes.
* The [`extension`](/en/docs/collections/entries/formats#extension) collection option (optional)
  * It defaults to `md`.

Looking at the above options, the entry file path can be constructed as follows:

* With i18n disabled:
  ```yaml
  /<folder>/<path>.<extension>
  ```
* With the `single_file` i18n structure
  ```yaml
  /<folder>/<path>.<extension>
  ```
* With the `multiple_files` i18n structure:
  ```yaml
  /<folder>/<path>.<locale>.<extension>
  ```
  When the `omit_default_locale_from_file_path` i18n option is set to `true`, the path depends on the locale:
  ```yaml
  /<folder>/<path>.<extension> # default locale
  /<folder>/<path>.<locale>.<extension> # other locales
  ```
* With the `multiple_folders` i18n structure:
  ```yaml
  /<folder>/<locale>/<path>.<extension>
  ```
  When the `omit_default_locale_from_file_path` i18n option is set to `true`, the path depends on the locale:
  ```yaml
  /<folder>/<path>.<extension> # default locale
  /<locale>/<folder>/<path>.<extension> # other locales
  ```
* With the `multiple_root_folders` i18n structure:
  ```yaml
  /<locale>/<folder>/<path>.<extension>
  ```
  When the `omit_default_locale_from_file_path` i18n option is set to `true`, the path depends on the locale:
  ```yaml
  /<folder>/<path>.<extension> # default locale
  /<locale>/<folder>/<path>.<extension> # other locales
  ```
