---
url: /en/docs/api/file-formats.md
description: >-
  Register custom file format parsers and formatters in Sveltia CMS for
  non-standard file types.
---

# Custom File Formats

Sveltia CMS comes with built-in support for common file formats like JSON, YAML, TOML and Markdown. However, you can also register your own custom parsers and formatters to handle different file types or formats.

## Overview

To register a custom file format, use the `registerCustomFormat` method on the [`CMS` object](/en/docs/api#accessing-the-cms-object):

```js
CMS.registerCustomFormat(name, extension, { fromFile, toFile });
```

### Parameters

* `name` (string): A unique name for the custom format. This name will be used to reference the format in collection configurations. A custom format with the same name as a built-in one, such as `json`, takes precedence over the built-in format, and registering a format with the same name again replaces the previous one.
* `extension` (string): The file extension associated with this format, without a leading dot (e.g., `json5`, `yaml`, `toml`).
* `fromFile` (function): A parser function that takes a string (the content of the file, trimmed and with line breaks normalized to `\n`) and returns a JavaScript object.
* `toFile` (function): A formatter function that takes a JavaScript object and returns a string (the content to be saved to the file). The output is trimmed and a trailing line break is added.

You can omit either `fromFile` or `toFile` if you only need to customize one direction (parsing or formatting). If you omit `fromFile`, the CMS will use the built-in parser for the format name, if any, such as `yaml` or `json`. Similarly, if you omit `toFile`, the CMS will use the built-in formatter. If the format name isn’t a built-in one, provide both functions: without `fromFile`, files in the format can’t be loaded, and without `toFile`, saving an entry fails with an error, leaving the file untouched. The CMS logs a warning to the browser console when such a function is missing. You must provide at least one of the two functions; otherwise an error is thrown.

The functions `fromFile` and `toFile` can also be asynchronous, allowing you to perform async operations if needed.

## Using Custom Formats

Once registered, the custom format can be used in your collection configurations by specifying the `format` property. For example:

::: code-group

```yaml{3} [YAML]
collections:
  - name: myCollection
    format: json5
    fields:
      - name: item1
        label: Item 1
```

```toml{3} [TOML]
[[collections]]
name = "myCollection"
format = "json5"

[[collections.fields]]
name = "item1"
label = "Item 1"
```

```json{5} [JSON]
{
  "collections": [
    {
      "name": "myCollection",
      "format": "json5",
      "fields": [
        {
          "name": "item1",
          "label": "Item 1"
        }
      ]
    }
  ]
}
```

```js{5} [JavaScript]
{
  collections: [
    {
      name: "myCollection",
      format: "json5",
      fields: [
        {
          name: "item1",
          label: "Item 1",
        },
      ],
    },
  ],
}
```

:::

You don’t need to specify the file `extension` in the collection configuration; the CMS will automatically use the extension of the registered format. If the collection has an `extension` option, it’s ignored in favor of the registered one.

## Examples

### YAML with Alternative Library

By default, Sveltia CMS uses the `yaml` library to parse and format YAML files. If you want to use the `js-yaml` library instead, you can register a custom formatter as follows:

```js
import YAML from 'js-yaml';

CMS.registerCustomFormat('yaml', 'yml', {
  fromFile: (text) => YAML.load(text),
  toFile: (data) => YAML.dump(data),
});
```

The file `extension` in the second argument is set to `yml` to match the default extension for YAML files. You can change it to `yaml` if you prefer.

### JSON5

The following example demonstrates how to register a custom formatter for the JSON5 format using the `json5` library:

```js
import JSON5 from 'json5';

CMS.registerCustomFormat('json5', 'json5', {
  fromFile: (text) => JSON5.parse(text),
  toFile: (data) => JSON5.stringify(data, null, 2),
});
```

### JSON with Additional Metadata

You can customize the behavior of the formatter functions. For example, you might want to add a timestamp and version number to the JSON file whenever it is saved:

```js
CMS.registerCustomFormat('json', 'json', {
  toFile: (data) => {
    const completeData = {
      ...data,
      last_updated: new Date().toISOString(),
      version: (data.version ?? 0) + 1,
    };

    return JSON.stringify(completeData, null, 2);
  },
});
```

An [event hook](/en/docs/api/events) is a better way to add metadata like timestamps, but this example illustrates how you can customize the formatter functions.

`fromFile` is omitted in this example, so the CMS will use the default `JSON.parse` method to parse JSON files.

### JavaScript Module

The following example demonstrates how to register a custom formatter for JavaScript modules that export data using `export default` syntax and parse it back into a JavaScript object:

```js
CMS.registerCustomFormat('mjs', 'js', {
  fromFile: (text) => JSON.parse(text.replace(/^export default (.+);$/s, '$1')),
  toFile: (data) => `export default ${JSON.stringify(data, null, 2)};`,
});
```

The file extension is set to `js` for demonstration purposes. You can use `mjs` if you prefer.

This example uses a simple regex to extract the JSON object from the `export default` statement. If you need a more robust solution for parsing JavaScript modules, consider using a library like `acorn` or `esbuild` to handle the parsing.

### Asynchronous Formatter

The parser and formatter functions can also be asynchronous. For example, you might want to format JSON data using a library like Prettier, which returns a promise:

```js
import Prettier from 'prettier';

CMS.registerCustomFormat('json', 'json', {
  toFile: async (data) => Prettier.format(data),
});
```

If you omit `fromFile`, the CMS will fall back to the built-in parser for the format name. In this case, the standard `JSON.parse` method will be used to parse JSON files.

### Custom Markdown Parser/Formatter

The following example demonstrates how to register a custom parser and formatter for Markdown files that follow a specific structure:

```js
CMS.registerCustomFormat('custom-markdown', 'md', {
  fromFile: (text) => {
    const regex =
      /^# (?<title>.+)\n\n!\[(?<alt>.*)\]\((?<src>.*)\)\n\n> (?<excerpt>.*)\n\n(?<body>[\s\S]*)$/;

    const {
      title = '',
      alt = '',
      src = '',
      excerpt = '',
      body = '',
    } = text.match(regex)?.groups ?? {};

    return {
      title,
      cover: { src, alt },
      excerpt,
      body,
    };
  },
  toFile: (data) => {
    const {
      title,
      cover: { src, alt },
      excerpt,
      body,
    } = data;

    return `# ${title}\n\n![${alt}](${src})\n\n> ${excerpt}\n\n${body}`;
  },
});
```

The above example uses a regular expression to parse the Markdown content into a structured object with `title`, `cover`, `excerpt`, and `body` fields. The formatter function then converts the structured object back into the Markdown format.

An example of a Markdown file that would be parsed by this custom format is as follows:

```markdown
# My First Post

![A beautiful sunrise](sunrise.jpg)

> This is a brief excerpt of my first post.

This is the body of my first post. It can contain multiple paragraphs, lists, and other Markdown elements.
```

The collection configuration for this custom format would look like this:

::: code-group

```yaml [YAML]
collections:
  - name: posts
    label: Posts
    thumbnail: cover.src
    folder: content/posts
    format: custom-markdown
    fields:
      - name: title
        label: Title
        widget: string
      - name: cover
        label: Cover Image
        widget: object
        fields:
          - name: src
            label: Source
            widget: image
          - name: alt
            label: Alt Text
            widget: string
      - name: excerpt
        label: Excerpt
        widget: text
      - name: body
        label: Body
        widget: richtext
```

```toml [TOML]
[[collections]]
name = "posts"
label = "Posts"
thumbnail = "cover.src"
folder = "content/posts"
format = "custom-markdown"
[[collections.fields]]
name = "title"
label = "Title"
widget = "string"
[[collections.fields]]
name = "cover"
label = "Cover Image"
widget = "object"
[[collections.fields.fields]]
name = "src"
label = "Source"
widget = "image"
[[collections.fields.fields]]
name = "alt"
label = "Alt Text"
widget = "string"
[[collections.fields]]
name = "excerpt"
label = "Excerpt"
widget = "text"
[[collections.fields]]
name = "body"
label = "Body"
widget = "richtext"
```

```json [JSON]
{
  "collections": [
    {
      "name": "posts",
      "label": "Posts",
      "thumbnail": "cover.src",
      "folder": "content/posts",
      "format": "custom-markdown",
      "fields": [
        {
          "name": "title",
          "label": "Title",
          "widget": "string"
        },
        {
          "name": "cover",
          "label": "Cover Image",
          "widget": "object",
          "fields": [
            {
              "name": "src",
              "label": "Source",
              "widget": "image"
            },
            {
              "name": "alt",
              "label": "Alt Text",
              "widget": "string"
            }
          ]
        },
        {
          "name": "excerpt",
          "label": "Excerpt",
          "widget": "text"
        },
        {
          "name": "body",
          "label": "Body",
          "widget": "richtext"
        }
      ]
    }
  ]
}
```

```js [JavaScript]
{
  collections: [
    {
      name: 'posts',
      label: 'Posts',
      thumbnail: 'cover.src',
      folder: 'content/posts',
      format: 'custom-markdown',
      fields: [
        {
          name: 'title',
          label: 'Title',
          widget: 'string',
        },
        {
          name: 'cover',
          label: 'Cover Image',
          widget: 'object',
          fields: [
            {
              name: 'src',
              label: 'Source',
              widget: 'image',
            },
            {
              name: 'alt',
              label: 'Alt Text',
              widget: 'string',
            },
          ],
        },
        {
          name: 'excerpt',
          label: 'Excerpt',
          widget: 'text',
        },
        {
          name: 'body',
          label: 'Body',
          widget: 'richtext',
        },
      ],
    },
  ],
}
```

:::

## Showcase

Real-world examples of custom file formats can be found in our [showcase](/en/showcase?feature=file-formats).
