Sveltia CMS is a Git-based lightweight headless CMS under active development as a modern, powerful, direct replacement for Netlify CMS (now Decap CMS). While some features are still missing, we’ve already solved over 265 issues reported in the predecessor’s repository, from critical bugs to top feature requests.
Built from the ground up, Sveltia CMS offers excellent UX, DX, performance, security and internationalization (i18n) support. Our numerous enhancements across the board ensure smooth daily workflows. This free, open source alternative to Netlify/Decap CMS is currently in public beta, with version 1.0 expected in early 2026.
Welcome to the only Netlify CMS successor you can trust!
Sveltia CMS was born in November 2022, when the progress of Netlify CMS was stalled for more than six months. @kyoshino’s clients wanted to replace their Netlify CMS instances without much effort, mainly to get better internationalization (i18n) support.
To achieve radical improvements in UX, performance, i18n and other areas, it was ultimately decided to build an alternative from the ground up, while ensuring an easy migration path from the other. After proving the idea with a rapid Svelte prototype, development was accelerated to address their primary use cases. The new product has since been named Sveltia CMS and released as open source software to encourage wider adoption.
We loved the simple, unique setup of Netlify CMS that turned a Git repository into a database with a single page app served from a CDN plus a plain YAML config file. In support of the Jamstack concept, we wanted to revive it, modernize it, and take it to the next level.
Due to its unfortunate abandonment in early 2022, Netlify CMS spawned 3 successors:
Sveltia CMS is the only project that doesn’t inherit the complexity, technical debt, and numerous bugs of Netlify CMS, which was launched in 2015. Our product is better by design:
This “total reboot” allows us to implement hundreds of improvements without getting stuck in a legacy system, establishing Sveltia CMS as a unparalleled successor to Netlify CMS.
As we continue to add more features, we hope that our product will eventually become an appearing headless CMS option for everyone, not just for existing Netlify/Decap CMS users.
Sveltia CMS is currently in beta, with version 1.0 (GA) scheduled for release in early 2026. Check our release notes and follow us on Bluesky for updates. See also our roadmap.
While we fix reported bugs as quickly as possible, usually within 24 hours, our overall progress may be slower than you think. The thing is, it’s not just a personal project of @kyoshino, but also a complicated system involving various kinds of activities that require considerable effort:
Netlify/Decap CMS users will definitely be pleased and surprised by the numerous improvements we have made, from the small to the large. Here’s what makes Sveltia CMS different. Look how serious we are!
Note: This lengthy section compares Sveltia CMS with both Netlify CMS and Decap CMS. Some of the listed issues may have been fixed in the current version of Decap CMS.
search configuration option is therefore ignored). It also avoids the slowness and potential API rate limit violations caused by hundreds of requests with Relation fields.[^14]<iframe> by default because it’s a performance overhead.[^179]We’ve made various improvements to help you get your work done faster and more efficiently:
publish_mode,[^75] and an unused logo_url.[^49]multiple option is enabled.[^239]Ctrl+S (Windows/Linux) or Command+S (macOS) key to save your time.pnpm audit, and frequent releases, unlike Netlify/Decap CMS where a number of high severity vulnerabilities remain unaddressed for a long time.[^33]cooldown option for ncu and the minimumReleaseAge option for pnpm to avoid upgrading to a version that was just released. These options help protect against npm supply chain attacks.sanitize_preview option was set to false by default for compatibility with Netlify/Decap CMS. This behaviour is documented and is not a bug, but it’s definitely not secure. In Sveltia CMS 0.105.0, we changed the default value to true, assuming that most users would prefer security over compatibility.unsafe-eval and unsafe-inline keywords are not needed in the script-src CSP directive.[^34]same-origin referrer policy is automatically set with a <meta> tag./admin) depending on the configuration. In such cases, the config file is loaded from the proper URL (/admin/config.yml) instead of a regular relative URL (./config.yml = /config.yml), which results in a 404 Not Found error.[^107]meta tag is automatically added to HTML to prevent the admin page from being indexed by search engines.[^174] Developers are still encouraged to manually add <meta name="robots" content="noindex"> to index.html, as not all crawlers support dynamically added tags. However, our solution should at least work with Google in case you forget to do so.window.CMS_MANUAL_INIT) will not result in a NotFoundError.[^251]window.CMS_MANUAL_INIT = true in your code.CMS.init() and other methods complete, accurate, up-to-date and annotated.[^190][^191][^192][^193][^227] This makes it easier to provide a site config object when manually initializing the CMS.The GitHub, GitLab, Gitea/Forgejo and Test backends are available in Sveltia CMS. For performance reasons, we don’t plan to support other backends.
use_graphql option to enable it for GitHub and GitLab.[^65]main, master or whatever) if not specified in the configuration file, preventing data loading errors due to a hardcoded fallback to master.[^95][^27] If a branch name is specified, it works as expected.[^232]DRAFT_MEDIA_FILES in file paths.[^222]Sveltia CMS has been built with a multilingual architecture from the very beginning. You can expect first-class internationalization (i18n) support, as it’s required by clients of maintainer @kyoshino, who himself was a long-time Japanese localizer for Mozilla and currently lives in the most diverse city in the world where 150+ languages are spoken.
{{locale}} template tag in the file path option, e.g. content/pages/about.{{locale}}.json or content/pages/{{locale}}/about.json. For backward compatibility, the global structure option only applies to folder collections, and the default i18n structure for file collections remains single file.i18n: duplicate field configuration so that changes made with these widgets are duplicated between locales.[^7][^68] The i18n configuration can normally be used for the subfields.multiple_folders_i18n_root i18n structure allows to have locale folders below the project root: /<locale>/<folder>/<path>.<extension>.[^182]omit_default_locale_from_filename i18n option allows to exclude the default locale from filenames. This option applies to entry collections with the multiple_files i18n structure enabled, as well as to file collection items with the file path ending with .{{locale}}.<extension>, aiming to support Zola’s multilingual sites. (Discussion)multiple_folders i18n structure.[^21]translationKey.[^81]clean_accents option is enabled for entry slugs, certain characters, such as German umlauts, will be transliterated.[^99]required field option accepts an array of locale codes in addition to a boolean, making the field required for a subset of locales when i18n support is enabled. For example, if only English is required, you could write required: [en]. An empty array is equivalent to required: false.{{locale}} template tag can be used in the preview_path collection option to provide site preview links for each language.[^63]widget: hidden along with default: '{{locale}}'.[^101]value_field Relation field option can contain a locale prefix like {{locale}}/{{slug}}, which will be replaced with the current locale. It’s intended to support i18n in Astro. (Discussion)DE as German, NL as Dutch, ZH as Chinese, and so on.single_file structure is used and a required field is not filled in any of the locales.[^55]icon: Choose a custom icon for each collection.[^3]thumbnail: Specify the field name for a thumbnail displayed on the entry list, like thumbnail: featuredImage.heroImage.src.images.*.src.[thumbnail, cover].thumbnail: [].limit: Specify the maximum number of entries that can be created in a folder collection.[^185]divider: Add dividers to the collection list.filter option for folder collections:value works as expected.[^93]value accepts null to match an undefined field value.value accepts an array to provide multiple possible values.[^151]pattern can be used instead of value to provide a regular expression, just like the view_filters collection option.[^153]summary:slug, path and preview_path collection options[^29]summary field option for the List and Object widgetsdefault transformation accepts a template tag like {{fields.slug | default('{{fields.title}}')}}, making it possible to fall back to a different field value. (Discussion)date transformation supports the time zone argument. The only available value is utc, which converts a date to UTC. This is useful if the specified DateTime field is local, but you want to force UTC in the entry slug, e.g. {{date | date('YYYYMMDD-HHmm', 'utc')}}. (Discussion)date transformation returns an empty string if an invalid date is given.[^176]{{title | upper | truncate(20)}}._headers and _redirects.format and frontmatter_delimiter options, which can be used to specify the file format, making it possible to have yaml-frontmatter, toml-frontmatter and json-frontmatter side by side.[^218]label defaults to the name value according to the Decap CMS document, while Netlify/Decap CMS actually throws a configuration error if the label option is omitted.path option for a folder collection, e.g. {{fields.state.name}}/{{slug}}.[^62]description collection option.[^79] Bold, italic, strikethrough, code and links are allowed.folder can be an empty string (or . or /) if you want to store entries in the root folder. This supports a typical VitePress setup.{{fields._slug}} to the slug collection option. This will display a special slug editor UI that looks like a standard string field, but the value will be used as the entry slug.{{fields._slug | localize}} to make the slug field editable and localizable.body field, an entry slug will be generated from a header in the body, if exists. This supports a typical VitePress setup.{{fields.date | date('YYYY-MM-DD')}} to generate a slug like 2025-01-23 from a DateTime field.sanitize_replacement (default: hyphen) rather than being removed.[^52]slug option accepts the trim option to remove leading and trailing replacement characters, such as hyphens, from an entry slug. The default value is true. Set to false to keep them.slug_length collection option to avoid deployment errors with Netlify or other platforms.[^25]path doesn’t affect the entry slugs stored with the Relation widget.[^137]sortable_fields option accepts a special slug value to allow sorting by entry slugs._index.md file, including localized ones like _index.en.md, within a folder collection.[^201] If the index_file option is not defined, these files will be hidden in a folder collection unless the path option is configured to end with _index and the extension is md.[^120]title field.[^152] In that case, an entry summary will be generated from a header in the Markdown body field, if exists, or from the entry slug, so the summary will never be an empty.[^161] This supports a typical VitePress and Docusaurus setup.[^230]summary, such as title, the entry list displays an updated summary after you save the entry.[^159]create: true option on a file collection because it’s useless.[^89]delete: true option on a file collection because the preconfigured files should not be deleted.readonly field option makes the field read-only. This is useful when a default value is provided and the field should not be editable by users.[^223] The option defaults to false except for the UUID widget, where it defaults to true.pattern option.[^82] For example, if you want to allow 280 characters or less in a multiline text field, you could write /^.{0,280}$/s (but you can now use the maxlength option instead.)data can be used as a field name without causing an error when saving the entry.[^180]<iframe> unless a custom preview stylesheet is registered.[^179]editor.visualEditing collection option. We don’t plan to support this option because it’s confusing, unnecessary and undocumented. (Plus, why camel case?)preview: false.[^126]null, instead of nothing (undefined), regardless of the required field option.[^45][^46][^44][^157]required: false makes data input optional, but doesn’t make data output optional.omit_empty_optional_fields: true in the data output options. This is useful if you have data type validations that expect undefined.[^156]HH:mm:ss instead of HH:mm for framework compatibility.body field.[^268]Sveltia CMS supports all the built-in widgets available in Netlify/Decap CMS. We have improved these widgets significantly while adding some new ones.
false by default, without raising a confusing validation error.[^45]false by default, rather than nothing.[^46]TypeError.[^257]rgb() function notation.picker_utc option is true.[^150]RangeError for formatting days of the month.[^250]default value supports the following template tags:{{locale}}: The current locale code.[^101]{{datetime}}: The current date/time in ISO 8601 format.[^102]{{uuid}}, {{uuid_short}} and {{uuid_shorter}}: A random UUID or its shorter version, just like the slug template tags.[^12]default value is saved when you create a file collection item, not just a folder collection item.[^78]root option.[^148]min and max options can be used separately. You don’t need to specify both to use either option.[^145]add_to_top option is not true, so you don’t have to scroll up each time to add new items.min and max options for the required option to work.default value.[^162]fields in a variable type object.[^163] In that case, only the typeKey (default: type) is saved in the output.thumbnail option allows developers to specify an Image field name for the list item thumbnail. Thumbnails are displayed alongside the summary in the collapsed item view. (Discussion)List [ Map { "key": "value" } ] if the summary option is not set.[^183]allow_remove and allow_reorder options can be used to prevent users from removing items and reordering them, respectively.[^272]sanitize_preview option defaults to true since Sveltia CMS 0.105.0.modes option.[^58] If you want to use the plain text editor by default, add modes: [raw, rich_text] to the field configuration.-) rather than an asterisk (*), which is the comment form’s default behaviour on GitHub and GitLab.[^296]image component can be inserted with a single click.image component allows users to add, edit or remove a link on an image.[^171] To disable this feature, add linked_images: false to the Markdown field options.code-block component is implemented just like a blockquote. You can simply convert a normal paragraph into a code block instead of adding a component.value_type option is int (default) or float, the required option is false, and the value is not entered, the field will be saved as null instead of an empty string.[^157] If value_type is anything else, the data type will remain a string.types and typeKey options) just like the List widget.[^226]required: false) can be manually added or removed with a checkbox.[^88] If unadded or removed, the required subfields won’t trigger validation errors,[^16] and the field will be saved as null.options_length option, which defaults to 20, is therefore ignored.[^76]slug can be used for value_field to show all available options instead of just one in some situations.[^91]{{cities.*.name}} can also be used for value_field.[^94]display_fields is displayed in the Preview Pane instead of value_field.search_fields option is optional in Sveltia CMS, as it defaults to display_fields, value_field or the collection’s identifier_field, which is title by default.value_field option is also optional in Sveltia CMS, as it defaults to {{slug}} (entry slugs).field option, which produces a single subfield but does not output the subfield name in the data, using the value_field: cities.*.name syntax. (Discussion)0.[^56]label is displayed in the Preview Pane instead of value.type option that accepts url or email as a value, which will validate the value as a URL or email.prefix and suffix string options, which automatically prepend and/or append the developer-defined value to the user-input value, if it’s not empty.before_input and after_input string options, which allow developers to display custom labels before and/or after the input UI.[^28] Markdown is supported in the value.prefix and suffix, respectively, which have different meaning in Sveltia CMS.multiple option, which can be set to true to allow multiple file uploads.[^239]min and max options are also available to limit the number of files that can be uploaded. Both accept positive integers. If min is not specified, it defaults to 0. If max is not specified, it defaults to Infinity.accept option allows files to be filtered by a comma-separated list of unique file type specifiers, in the same way as the HTML accept attribute for <input type="file">.[^216]public_folder contains {{slug}} and you’ve edited a slug field (e.g. title) of a new entry after uploading an asset, the updated slug will be used in the saved asset path.[^140] Other dynamic template tags such as {{filename}} will also be populated as expected.[^141]summary is displayed correctly when it refers to a Relation field[^36] or a simple List field.summary template tags support transformations, e.g. {{fields.date | date('YYYY-MM-DD')}}.collapsed option accepts the value auto to automatically collapse the widget if any of its subfields are filled out. The same applies to the minimize_collapsed option for the List widget.dropdown_threshold option for the relation and select widgets.minlength and maxlength options, which allow developers to specify the minimum and maximum number of characters required for input without having to write a custom regular expression with the pattern option. A character counter is available when one of the options is given, and a user-friendly validation error is displayed if the condition is not met.compute widget allows to reference the value of other fields in the same collection, similar to the summary property for the List and Object widgets.[^104] Use the value property to define the value template, e.g. posts-{{fields.slug}}. (Example)value property also supports a value of {{index}}, which can hold the index of a list item. (Example)keyvalue widget allows users to add arbitrary key-value string pairs to a field.[^123]uuid widget with the following properties:[^12]prefix: A string to be prepended to the value. Default: an empty string.use_b32_encoding: Whether to encode the value with Base32. Default: false./ as expected.[^48]media_libraries option.[^195]multiple File/Image widget option to true. For compatibility with other media libraries, the media_library.config.multiple option is also supported.max_file_size option for the File/Image widget can be defined within the global media_library option, using default as the library name. It applies to all File/Image entry fields, as well as direct uploads to the Asset Library. The option can also be part of the new media_libraries option.slug option, use the slugify_filename default media library option.multiple option is enabled, the output is always an array of strings, regardless of whether a single file is selected.[^299]script-src CSP directive is not required for the widget to work, as we implemented the Cloudinary media library without using their hosted widget script.media_folder option is not defined.[^295]media_folder are scanned recursively and displayed in the Asset Library.[^249]media_folder can be an empty string (or . or /) if you want to store assets in the root folder.logo_url property is displayed on the global application header and the browser tab (favicon).[^134] A smaller logo is also correctly positioned on the authentication page.[^135]logout_redirect_url global option.[^283]CMS.registerEditorComponent() accepts a component definition with the icon property. Developers can specify a Material Symbols icon name just like custom collection icons.fromBlock function can be omitted if the pattern regex contains named capturing groups for the values.Sent invalid data to remark[^280] or onValidate is not a function.[^281]locale configuration option is ignored and CMS.registerLocale() is not required.label and label_singular are not converted to lowercase, which is especially problematic in German, where all nouns are capitalized.[^98]We are working to make Sveltia CMS compatible with Netlify/Decap CMS wherever possible so that more users can seamlessly switch to our modern alternative. In some casual use cases, Sveltia CMS can be used as a drop-in replacement for Netlify/Decap CMS with just a one-line code update.
However, 100% feature parity is never planned, and some features are still missing or will not be added due to performance, deprecation and other factors. Look at the compatibility info below to see if you can migrate now or in the near future.
These Netlify/Decap CMS features are not yet implemented in Sveltia CMS. We are working hard to add them before the 1.0 release. Check our release notes and Bluesky for updates.
CMS.registerEditorComponent)CMS.registerWidget)CMS.registerPreviewTemplate) (#51)CMS.registerEventListener) (#167)Localization, documentation and a demo site will all be prepared once the 1.0 Release Candidate is ready.
Due to the complexity, we have decided to defer the following features to the 1.x or 2.0 release due mid-2026. Netlify/Decap CMS has dozens of open issues with these collaboration and beta features — we want to implement them the right way.
The following Netlify/Decap CMS features will not be added to Sveltia CMS, primarily due to deprecation and performance considerations.
index.html themselves. Note: We don’t support Netlify Identity Widget; the favicon can be specified with the logo.src option.sortableFieldsdateFormat, timeFormat, pickerUtceditorComponentsvalueTypedisplayFields, searchFields, valueFieldtime_format: false option instead.allow_multiple option for the File and Image widgets: It’s a confusing option that defaults to true, and there is a separate option called media_library.config.multiple. We have added the new multiple option instead, which is more intuitive and works with all media libraries.use_secure_url option for the Cloudinary media library: Insecure URLs should never be used.public_folder option: Such configuration is not recommended, as stated in the Netlify/Decap CMS document.searchuse_graphqloptions_lengthnetlify-cms-proxy-server, decap-server or the local_backend option.locale option and CMS.registerLocale() method: Sveltia CMS automatically detects the user’s preferred language and changes the UI locale as mentioned above.CMS object: This includes custom backends and custom media libraries, if any. We may support these features in the future, but our implementation would likely be incompatible with Netlify/Decap CMS.There are some differences in behaviour between Sveltia CMS and Netlify/Decap CMS that may affect your existing configuration or content.
format, date_format and time_format options for DateTime fields, as well as any date formatting in summary string transformations.slugify_filename default media library option.omit_empty_optional_fields output option if needed.localhost or 127.0.0.1 URLs. If you’re running your own remote server and serving content over HTTP, the CMS will not work. We recommend obtaining a TLS certificate from Let’s Encrypt.sanitize_preview option for the Markdown widget is set to true by default to prevent potential XSS attacks via entry previews. We recommend keeping this option enabled unless disabling it fixes a broken preview and you fully trust all users of your CMS.Let us know if you have encounter any compatibility issues that are not listed here.
While Sveltia CMS is built with Svelte, the application is framework-agnostic. It’s a small, compiled, vanilla JavaScript bundle that manages content in a Git repository directly via an API. It doesn’t interact with the framework that builds your site.
As with Netlify/Decap CMS, you can use Sveltia CMS with any framework or static site generator (SSG) that loads static files during the build process. This includes Astro, Eleventy, Hugo, Jekyll, Next.js, SvelteKit, VitePress, and more.
We have added support for features and file structures used in certain frameworks and i18n libraries, such as index file inclusion and slug localization for Hugo, i18n support for Astro and Zola, and some enhancements for VitePress. Let us know if your framework has specific requirements.
Sveltia CMS supports the GitHub, GitLab and Gitea/Forgejo backends. The Test backend is also available for local testing. There are a few differences compared to Netlify/Decap CMS:
base_url and api_root backend options is set to https://gitea.com (public free service) instead of https://try.gitea.io (test instance).Sveitia CMS works with all modern browsers, but there are a few limitations because it utilizes some new web technologies:
These options were added to Sveltia CMS 0.x but are now deprecated and will be removed in version 1.0:
automatic_deployments backend option: Use the new skip_ci option instead, which is more intuitive. automatic_deployments: false is equivalent to skip_ci: true, and automatic_deployments: true is equivalent to skip_ci: false.save_all_locales i18n option: Use the initial_locales option instead, which provides more flexibility. save_all_locales: false is equivalent to initial_locales: all.yaml_quote collection option: yaml_quote: true is equivalent to quote: double in the new YAML format options.read_only UUID widget option: Use the readonly common field option instead, which defaults to true for the UUID widget.The deprecated logo_url option will be removed in the future. Use the new logo.src option instead.
Sveltia CMS provides partial compatibility with Static CMS, a now-defunct fork of Netlify CMS. Since Static CMS was archived over a year ago, we don’t plan to implement additional compatibility beyond what’s listed below. However, we may still adopt some of their features that we find useful.
Static CMS made some breaking changes while Sveltia CMS mostly follows Netlify/Decap CMS, so you should review your configuration carefully.
sortable_fields, view_filters and view_groups options with the new default option. We still support the legacy Netlify/Decap CMS format as well, so you can use either format for these options.media_folders, these folders will be displayed in the Asset Library and Select File/Image dialog. We plan to implement the display of subfolders within a configured folder in Sveltia CMS 2.0. We don’t plan to support the folder_support and display_in_navigation options for media_library; subfolders will be displayed with no configuration. (#301)logo_link global option will not be supported. Use display_url or site_url instead.yaml global option will not be supported, as Sveltia CMS doesn’t expose the underlying yaml library options for forward compatibility reasons. However, we do have some data output options, including YAML indentation and quotes.enforce_required_non_default i18n option will not be supported. Sveitia CMS enforces required fields in all locales by default. However, the initial_locales i18n option allows users to disable non-default locales if needed. Developers can also specify a subset of locales with the required field option, e.g. required: [en].prefix and suffix options for the Boolean, Number and String widgets are implemented as before_input and after_input in Sveltia CMS, respectively. Our prefix and suffix options for the String widget are literally a prefix and suffix to the value.multiple option for the File and Image widgets is supported in Sveltia CMS, along with the min and max options.field (singular) option to produce a single subfield with no name output.CMS.registerIcon() will not be supported, as Sveltia CMS includes the Material Symbols font for custom collection icons that doesn’t require manual registration.Currently, Sveltia CMS is primarily intended for existing Netlify/Decap CMS users. If you don’t have it yet, follow their document to add it to your site and create a configuration file first. Skip the Choosing a Backend page and configure the GitHub, GitLab or Gitea/Forgejo backend instead. Then migrate to Sveltia CMS as described below.
Or try one of the starter kits for popular frameworks created by community:
The Netlify/Decap CMS website has more templates and examples. You can probably use one of them and switch to Sveltia CMS. (Note: These third-party resources are not necessarily reviewed by the Sveltia CMS team.)
Unfortunately, we are unable to provide installation and setup support at this time. As the product evolves, we’ll provide comprehensive documentation, a built-in configuration editor and official starter kits to make it easier for everyone to start using Sveltia CMS.
[!IMPORTANT] Take a look at the compatibility info above first. Some Netlify/Decap CMS features are not yet implemented or will not be added to Sveltia CMS, meaning you may not be able to migrate right away.
If you’re already using Netlify/Decap CMS with the GitHub, GitLab or Gitea/Forgejo backend and don’t have any unsupported features like editorial workflow or nested collections, migrating to Sveltia CMS is super easy — it works as a drop-in replacement.
Open /admin/index.html locally with an editor like VS Code and replace the CMS <script> tag with the new one:
<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
From Netlify CMS:
-<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
+<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
From Decap CMS:
-<script src="https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js"></script>
+<script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"></script>
Next, let’s test Sveltia CMS on your local machine. If everything looks good, push the change to your repository.
You can now open https://[hostname]/admin/ as usual to start editing. There is even no authentication process if you’re already signed in with a backend on Netlify/Decap CMS because Sveltia CMS uses your auth token stored in the browser. Simple enough!
For a better DX, we recommend setting up the JSON schema for the site configuration file in your code editor. If you have the YAML extension installed, VS Code may automatically apply the outdated Netlify CMS config schema to config.yml. To use the latest Sveltia CMS config schema instead, you need to specify its URL.
If you’re using any features listed in the current limitations section, you’ll need to wait until they are implemented in Sveltia CMS. We’re working hard to add these features in the coming months.
If you’re using any features that are not going to be implemented, you’ll need to find a workaround. For example, if you’re on Azure or Bitbucket, consider migrating to GitHub, GitLab, Gitea or Forgejo. See the next section if you’re a Git Gateway user.
Sveltia CMS does not support the Git Gateway backend due to performance limitations. If you don’t care about user management with Netlify Identity, you can use the GitHub or GitLab backend instead. Make sure you install an OAuth client on GitHub or GitLab in addition to updating your configuration file. As noted in the document, Netlify is still able to facilitate the auth flow.
To allow other people to edit content, simply invite them to your GitHub repository with the write role assigned. Please note, however, that Sveltia CMS hasn’t implemented any mechanisms to prevent conflicts in multi-user scenarios.
Once you have migrated from the Git Gateway and Netlify Identity combo, you can remove the Netlify Identity Widget script tag from your HTML:
-<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
If you want to stay with Git Gateway and Netlify Identity, unfortunately you can’t migrate to Sveltia CMS right now. We plan to develop an alternative solution in the future.
For advanced users, we have also made the bundle available as an npm package. You can install it by running npm i @sveltia/cms or pnpm add @sveltia/cms on your project. The manual initialization flow with the init method is the same as for Netlify/Decap CMS. Just update the import statement if you’re migrating:
-import CMS, { init } from 'decap-cms-app';
+import CMS, { init } from '@sveltia/cms';
Updating Sveltia CMS is automatic, unless you include a specific version in the <script> source URL or use the npm package. Whenever you (re)load the CMS, the latest version will be served via UNPKG. The CMS also periodically checks for updates and notifies you when a new version is available. After the product reaches GA, you could use a semantic version range (^1.0.0) like Netlify/Decap CMS.
If you’ve chosen to install with npm, updating the package is your responsibility. We strongly recommend using ncu or a service like Dependabot to keep dependencies up to date. Otherwise, you’ll miss important bug fixes and new features. (ProTip: We update our dependencies using ncu -u && pnpm up at least once a week.)
You can host your Sveltia CMS-managed site anywhere, such as Cloudflare Pages or GitHub Pages. But moving away from Netlify means you can no longer sign in with GitHub or GitLab via Netlify. Instead, you can use our own OAuth client, which can be easily deployed to Cloudflare Workers, or any other 3rd party client made for Netlify/Decap CMS.
You can also generate a personal access token (PAT) on GitHub or GitLab, and use it to sign in. No OAuth client is needed. While this method is convenient for developers, it’s better to set up an OAuth client if your CMS instance is used by non-technical users because it’s more user-friendly and secure.
Sveltia CMS provides a full JSON schema for the configuration file, so you can get autocomplete and validation in your favourite code editor while editing the site configuration. The schema is generated from the source and always up to date with the latest CMS version.
If you use VS Code, you can enable it for the YAML configuration file by installing the YAML extension and adding the following comment to the top of config.yml:
# yaml-language-server: $schema=https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json
If your configuration is in JSON format (see the next section), no extension is needed. Just add the following line to the top of config.json, within the curly braces:
"$schema": "https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json",
Alternatively, you can add the following to your project’s VS Code settings file at .vscode/settings.json, within the outer curly braces:
// For YAML config file
"yaml.schemas": {
"https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json": ["/static/admin/config.yml"]
}
// For JSON config file
"json.schemas": [
{
"fileMatch": ["/static/admin/config.json"],
"url": "https://unpkg.com/@sveltia/cms/schema/sveltia-cms.json"
}
]
The configuration file location varies by framework and project structure, so adjust the path accordingly. For example, if you use Astro, the file is typically located in the /public/admin/ directory.
If you use another code editor, check its documentation for how to enable JSON schema support for YAML or JSON files.
Sveltia CMS supports a configuration file written in the JSON format in addition to the standard YAML format. This allows developers to programmatically generate the CMS configuration to enable bulk or complex collections. To do this, simply add a <link> tag to your HTML, just like a custom YAML config link, but with the type application/json:
<link href="path/to/config.json" type="application/json" rel="cms-config-url" />
Alternatively, you can manually initialize the CMS with a JavaScript configuration object.
With Sveltia CMS, developers can modularize the site configuration. Just provide multiple config links and the CMS will automatically merge them in the order of <link> tag appearance. It’s possible to use YAML, JSON or both.
<link href="/admin/config.yml" type="application/yaml" rel="cms-config-url" />
<link href="/admin/collections/authors.yml" type="application/yaml" rel="cms-config-url" />
<link href="/admin/collections/pages.yml" type="application/yaml" rel="cms-config-url" />
<link href="/admin/collections/posts.yml" type="application/yaml" rel="cms-config-url" />
Both standard application/yaml and non-standard text/yaml are acceptable for the YAML config link type.
Limitation: YAML anchors, aliases and merge keys only work if they are in the same file. This is because the files are parsed as separate JavaScript objects and then merged using the deepmerge library.
If you get an “Authentication Aborted” error when trying to sign in to GitHub, GitLab or Gitea/Forgejo using the authorization code flow, you may need to check your site’s Cross-Origin-Opener-Policy. The COOP header is not widely used, but it’s known to break the OAuth flow with a popup window. If that’s your case, changing same-origin to same-origin-allow-popups solves the problem. (Discussion)
Sveltia CMS has simplified the local repository workflow by removing the need for additional configuration (the local_backend option) and a proxy server (netlify-cms-proxy-server or decap-server), thanks to the File System Access API available in some modern browsers.
Here are the workflow steps and tips:
name: git-gateway won’t work. Use github, gitlab or gitea for the name along with the repo definition. If you haven’t determined your repository name yet, just use a tentative name.npm run dev or pnpm dev.http://localhost:[port]/admin/index.html with Chrome or Edge.127.0.0.1 addresses can also be used instead of localhost./admin/, use the appropriate path.git init) or GUI, and the hidden .git folder exists.git diff or a GUI like GitHub Desktop to see if the produced changes look good.http://localhost:[port]/ to check the rendered pages.If you have migrated from Netlify/Decap CMS and are happy with the local repository workflow of Sveltia CMS, you can remove the local_backend option from your configuration and uninstall the proxy server. If you have configured a custom port number with the .env file, you can remove it as well.
Note that, as with Netlify/Decap CMS, the local repository support in Sveltia CMS doesn’t perform any Git operations. You have to manually fetch, pull, commit and push all changes using a Git client. Additionally, you’ll need to reload the CMS after modifying the configuration file or retrieving remote updates.
In the future, it will probably be possible to commit changes locally. The Netlify/Decap CMS proxy server actually has an experimental, undocumented Git mode that allows it.[^131] (Discussion) We also plan to use the newly available File System Observer API to detect changes and eliminate the need for manual reloads.
In the Brave browser, you must enable the File System Access API with an experiment flag to take advantage of the local repository workflow.
brave://flags/#file-system-access-api in a new browser tab.You can specify an icon for each collection for easy identification in the collection list. You don’t need to install a custom icon set because the Material Symbols font file is already loaded for the application UI. Just pick one of the 2,500+ icons:
config.yml as the new icon property, like the example below.fields:
- name: tags
label: Tags
icon: sell # or any icon name
create: true
folder: content/tags
With Sveltia CMS, developers can add dividers to the collection list to distinguish between different types of collections. To do so, insert a new item with the divider option set to true. In VS Code, you may receive a validation error if config.yml is treated as a Netlify CMS configuration file. You can resolve this issue by using our JSON schema.
collections:
- name: products
...
- divider: true
- name: pages
...
The singleton collection also supports dividers.
This is actually not new in Sveltia CMS but rather an undocumented feature in Netlify/Decap CMS.[^4] You can specify media and public folders for each collection that override the global media folder. Well, it’s documented, but that’s probably not what you want.
Rather, if you’d like to add all the media files for a collection in one single folder, specify both media_folder and public_folder instead of leaving them empty. The trick is to use a project relative path starting with a slash for media_folder like the example below. You can try this with Netlify/Decap CMS first if you prefer.
media_folder: static/media # leading slash is optional
public_folder: /media
collections:
- name: products
label: Products
folder: content/products
media_folder: /static/media/products # make sure to append a slash
public_folder: /media/products
In Sveltia CMS, those collection media folders are displayed prominently for easier asset management. We recommend setting media_folder and public_folder for each collection if it contains one or more File/Image fields.
Sveltia CMS has extended the sortable_fields collection option to allow developers to define the field name and direction to be used for sorting entries by default. Our implementation is compatible with Static CMS. This is especially useful if you want to show entries sorted by date from new to old:
collections:
- name: posts
sortable_fields:
fields: [title, published_date, author]
default:
field: published_date
direction: descending # default: ascending
For backward compatibility with Netlify/Decap CMS, sortable_fields with a field list (an array) will continue to work.
For backward compatibility with Static CMS, the direction option accepts title case values: Ascending and Descending. However, None is not supported and has the same effect as ascending.
Before this feature, Hugo’s special _index.md file was hidden in a folder collection, and you had to create a file collection to manage the file, since it usually comes with a different set of fields than regular entry fields. Now, with the new index_file option, you can add the index file to the corresponding folder collection, above regular entries, for easier editing:
collections:
- name: posts
label: Blog posts
folder: content/posts
fields: # Fields for regular entries
- { name: title, label: Title }
- { name: date, label: Published Date, widget: datetime }
- { name: description, label: Description }
- { name: body, label: Body, widget: markdown }
index_file:
fields: # Fields for the index file
- { name: title, label: Title }
- { name: body, label: Body, widget: markdown }
Here is an example of full customization. All options are optional.
index_file:
name: _index # File name without a locale or extension. Default: _index
label: Index File # Human-readable file label. Default: Index File
icon: home # Material Symbols icon name. Default: home
fields: # Fields for the index file. If omitted, regular entry fields are used
...
editor:
preview: false # Hide the preview pane if needed. Default: true
If your regular entry fields and index file fields are identical and you don’t need any options, simply write:
index_file: true
Note that the special index file is placed right under the folder, regardless of the collection’s path option. For example, if the path is {{year}}/{{slug}}, a regular entry would be saved as content/posts/2025/title.md, but the index file remains at content/posts/_index.md.
The singleton collection is an unnamed, non-nested variant of a file collection that can be used to manage a set of pre-defined data files. Singleton files appear in the content library’s sidebar under the Files group, and users can open the Content Editor directly without navigating to a file list. (If there are no other collections, the singleton collection appears as a regular file collection on desktop.)
To create this special file collection, add the new singletons option, along with an array of file definitions, to the root level of your site configuration.
This is a conventional file collection:
collections:
- name: data
label: Data
files:
- name: home
label: Home Page
file: content/home.yaml
icon: home
fields: ...
- name: settings
label: Site Settings
file: content/settings.yaml
icon: settings
fields: ...
It can be converted to the singleton collection like this:
singletons:
- name: home
label: Home Page
file: content/home.yaml
icon: home
fields: ...
- divider: true # You can add dividers
- name: settings
label: Site Settings
file: content/settings.yaml
icon: settings
fields: ...
If you want to reference a singleton file with a Relation field, use _singletons (note an underscore prefix) as the collection name.
Alt+1Alt+2Ctrl+F (Windows/Linux) or Command+F (macOS)Ctrl+E (Windows/Linux) or Command+E (macOS)Ctrl+S (Windows/Linux) or Command+S (macOS)EscapeStandard keyboard shortcuts are also available in the Markdown editor, including Ctrl+B/Command+B for bold text, Ctrl+I/Command+I for italics, and Tab to indent a list item.
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.
i18n global or collection option (optional)structure and omit_default_locale_from_filename options affect the entry file path.folder collection option (required)path collection option (optional){{slug}}, which is the slug collection option value.slug collection option (optional){{title}}, which is the entry’s title field value’s slugified version.extension collection option (optional)md.Looking at the above options, the entry file path can be constructed as follows:
/<folder>/<path>.<extension>
single_file i18n structure/<folder>/<path>.<extension>
multiple_files i18n structure:/<folder>/<path>.<locale>.<extension>
When the omit_default_locale_from_filename i18n option is set to true, the path depends on the locale:/<folder>/<path>.<extension> # default locale
/<folder>/<path>.<locale>.<extension> # other locales
multiple_folders i18n structure:/<folder>/<locale>/<path>.<extension>
multiple_folders_i18n_root i18n structure:/<locale>/<folder>/<path>.<extension>
The configuration for a file collection and singleton collection is much simpler, as it only requires the file option to specify the complete file path, including the folder, filename and extension. It can also include the {{locale}} template tag for i18n support.
Sveltia CMS comes with a handy translation API integration so that you can translate any text field from another locale without leaving the Content Editor. Currently, the following services are supported:
Google’s API is very fast and offers a free tier. Other LLMs may produce more natural translations, but they are slower and require a paid plan. Choose the one that best fits your needs.
To enable the quick translation feature:
Note that the Translation button on the pane header only translates empty fields, while in-field Translation buttons override any filled text.
You can also provide your API keys in the Settings dialog or change the default translation service. API keys are stored in the browser’s local storage, so you don’t need to enter them every time.
If you don’t want some text to be translated, use the HTML translate attribute or notranslate class:
<div translate="no">...</div>
<span class="notranslate">...</span>
For Anthropic and OpenAI, you can also use the notranslate comment to exclude specific parts of Markdown content from translation:
<!-- notranslate -->...<!-- /notranslate -->
{/* notranslate */}...{/* /notranslate */}
Earlier versions of Sveltia CMS included DeepL integration, but we had to disable it due to an API limitation. More translation services will be added in the future.
In Sveltia CMS, it’s possible to localize entry slugs (filenames) if the i18n structure is multiple_files or multiple_folders. All you need is the localize filter for slug template tags:
i18n:
structure: multiple_folders
locales: [en, fr]
slug:
encoding: ascii
clean_accents: true
collections:
- name: posts
label: Blog posts
create: true
folder: content/posts
slug: '{{title | localize}}' # This does the trick
format: yaml
i18n: true
fields:
- name: title
label: Title
widget: string
i18n: true
With this configuration, an entry is saved with localized filenames, while the default locale’s slug is stored in each file as an extra translationKey property, which is used in Hugo’s multilingual support. Sveltia CMS and Hugo read this property to link localized files.
content/posts/en/my-trip-to-new-york.yamltranslationKey: my-trip-to-new-york
title: My trip to New York
content/posts/fr/mon-voyage-a-new-york.yamltranslationKey: my-trip-to-new-york
title: Mon voyage à New York
You can customize the property name and value for a different framework or i18n library by adding the canonical_slug option to your top-level or collection-level i18n configuration. The example below is for @astrolicious/i18n, which requires a locale prefix in the value (discussion):
i18n:
canonical_slug:
key: defaultLocaleVersion # default: translationKey
value: 'en/{{slug}}' # default: {{slug}}
For Jekyll, you may want to use the ref property:
i18n:
canonical_slug:
key: ref
Developers can specify locales to be enabled by default when users create a new entry draft, using the initial_locales i18n option, which accepts a locale list, default (default locale only) or all (all locales).
The default locale is always enabled, even if it’s excluded from initial_locales, while other locales can be enabled or disabled by users in the Content Editor through the three-dot menu in the top right corner, if this i18n option is defined.
The following example disables German by default, but users can manually enable it if needed. Users can also disable French, which is enabled by default.
i18n:
structure: multiple_files
locales: [en, fr, de]
default_locale: en
initial_locales: [en, fr]
By default, the slug for a new entry file will be generated based on the entry’s title field. Or, you can specify the collection’s slug option to use the file creation date or other fields. While the behaviour is generally acceptable and SEO-friendly, it’s not useful if the title might change later or if it contains non-Latin characters like Chinese. In Sveltia CMS, you can easily generate a random UUID for a slug without a custom widget!
It’s simple — just specify {{uuid}} (full UUID v4), {{uuid_short}} (last 12 characters only) or {{uuid_shorter}} (first 8 characters only) in the slug option. The results would look like 4fc0917c-8aea-4ad5-a476-392bdcf3b642, 392bdcf3b642 and 4fc0917c, respectively.
collections:
- name: members
label: Members
slug: '{{uuid_short}}' # or {{uuid}} or {{uuid_shorter}}
The traditional media_library option allows developers to configure only one media library:
media_library:
name: default
config:
max_file_size: 1024000
Sveltia CMS has added support for multiple media libraries with the new media_libraries option so you can mix up the default media library (your repository), Cloudinary and Uploadcare. The new option can be used as a global option as well as a File/Image field option.
media_libraries:
default:
config:
max_file_size: 1024000 # default: Infinity
slugify_filename: true # default: false
transformations: # See the next section
cloudinary:
config:
cloud_name: YOUR_CLOUD_NAME
api_key: YOUR_API_KEY
output_filename_only: true
uploadcare:
config:
publicKey: YOUR_PUBLIC_KEY
settings:
autoFilename: true
defaultOperations: '/resize/800x600/'
Similar to the conventional media_library option, the unified media_libraries option can also be defined for each File/Image field. This allows you to use different media library configurations for different fields. For example, you can optimize images for upload in one field while using the default settings in another:
fields:
- name: cover
label: Cover Image
widget: image
media_libraries:
default:
config:
transformations: # See the next section
Ever wanted to prevent end-users from adding huge images to your repository? The built-in image optimizer in Sveltia CMS makes developers’ lives easier with a simple configuration like this:
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
Then, whenever a user selects images to upload, those images are automatically optimized, all within the browser. Raster images such as JPEG and PNG are converted to WebP format and resized if necessary. SVG images are minified using the SVGO library.
In case you’re not aware, WebP offers better compression than conventional formats and is now widely supported across major browsers. So there is no reason not to use WebP on the web.
media_libraries option can be global at the root level of config.yml, which applies to both entry fields and the Asset Library, or field-specific for the File/Image widgets.raster_image applies to any supported raster image format: avif, bmp, gif, jpeg, png and webp. If you like, you can use a specific format as key instead of raster_image.width and height options are the maximum width and height, respectively. If an image is larger than the specified dimension, it will be scaled down. Smaller images will not be resized.The Select File/Image dialog includes some stock photo providers for convenience, but sometimes these may be irrelevant. Developers can hide them with the following configuration:
media_libraries:
stock_assets:
providers: []
If you write blog posts, for example, you may want to categorize them with taxonomies, often called tags, categories, labels or keywords. With Sveltia CMS, there are several ways to implement this feature, depending on your needs.
If you don’t have a predefined list of tags, you can use a simple List field. This configuration will produce a newline-separated text field where users can enter tags freely:
collections:
- name: posts
label: Blog Posts
label_singular: Blog Post
folder: content/posts
create: true
fields:
- name: title
label: Title
- name: tags
label: Tags
widget: list
- name: body
label: Body
widget: markdown
If you have a small number of predefined tags, you can use a Select field. This configuration will produce a dropdown list where users can select one or more tags:
fields:
- name: tags
label: Tags
widget: select
multiple: true
options:
- { label: Travel, value: travel }
- { label: Food, value: food }
- { label: Technology, value: technology }
- { label: Lifestyle, value: lifestyle }
If you want more flexibility, you can create a separate collection for tags and reference it using a Relation field from your blog post collection. This approach allows you to:
This configuration will also produce a dropdown list where users can select one or more tags:
fields:
- name: tags
label: Tags
widget: relation
multiple: true
collection: tags
search_fields: [title]
display_fields: [title]
value_field: '{{slug}}'
And here is an example of the corresponding tag collection:
collections:
- name: tags
label: Tags
label_singular: Tag
folder: content/tags
create: true
fields:
- name: title
label: Title
- name: description
label: Description
widget: text
required: false
- name: image
label: Image
widget: image
required: false
Note that it’s not currently possible to add new tags on the fly while editing a blog post. You have to create them in the tag collection first. This issue will be resolved in the future. (#493)
Sveltia CMS allows users to edit files without extensions. Examples include _headers and _redirects, which are used by some static site hosting providers, such as Netlify, GitLab Pages and Cloudflare Pages. Since the body field is saved without the field name when using the default yaml-frontmatter format, you can use the following configuration to edit these files in the Content Editor:
collections:
- name: config
label: Site Configuration
editor:
preview: false
files:
- name: headers
label: Headers
file: static/_headers # The path varies by framework
fields:
- name: body
label: Headers
widget: code # Can also be `text`
output_code_only: true
allow_language_selection: false
- name: redirects
label: Redirects
file: static/_redirects # The path varies by framework
fields:
- name: body
label: Redirects
widget: code # Can also be `text`
output_code_only: true
allow_language_selection: false
Sveltia CMS allows you to edit and save a list at the top-level of a data file, without a field name. All you need to do is create a single List field with the new root option set to true. The configuration below reproduces this Jekyll data file example:
collections:
- name: data
label: Data Files
files:
- name: members
label: Member List
file: _data/members.yml
icon: group
fields:
- name: members
label: Members
label_singular: Member
widget: list
root: true # This does the trick
fields:
- name: name
label: Name
- name: github
label: GitHub account
It also works with a singleton:
singletons:
- name: members
label: Member List
file: _data/members.yml
icon: group
fields:
- name: members
label: Members
label_singular: Member
widget: list
root: true # This does the trick
fields:
- name: name
label: Name
- name: github
label: GitHub account
Note: The root option is ignored if the file or singleton contains multiple fields. You can still have subfields under the List field.
It may be worth mentioning this topic here because the current Decap CMS doc about the DateTime widget is unclear. By default, a DateTime field lets users pick both date and time, but developers can change the input type if needed.
Set time_format to false to hide the time picker and make the input date only:
- label: Start Date
name: startDate
widget: datetime
time_format: false
Set date_format to false to hide the date picker and make the input time only:
- label: Start Time
name: startTime
widget: datetime
date_format: false
We understand that this configuration may be a bit confusing, but it’s necessary to maintain backward compatibility with Netlify CMS. We plan to add the type option to the DateTime widget and introduce new input types: year, month and week.
This tip is not really specific to Sveltia CMS, but some developers have asked the maintainer about it:
In the Markdown editor, pressing Shift+Enter inserts a soft line break (\n). We can’t change the behaviour to add a hard line break (<br>) — it’s a limitation of the underlying Lexical framework. However, if you look at the preview, you may notice that soft line breaks are rendered as hard line breaks. That’s because the preview is using the Marked library with the breaks option enabled, which mimics how comments are rendered on GitHub.
Chances are the Markdown parser you use for your frontend can do the same:
breaks optionbreaks optionsimpleLineBreaks optionhtml.WithHardWraps optionhard_wrap option with the GFM parserSveltia CMS supports some data output options, including JSON/YAML formatting preferences, at the root level of the configuration file. The default options are listed below:
output:
omit_empty_optional_fields: false
encode_file_path: false # true to URL-encode file paths for File/Image fields
json:
indent_style: space # or tab
indent_size: 2
yaml:
quote: none # or single or double
indent_size: 2
indent_sequences: true # false for compact style
Content is generally saved as key-value pairs in a file, where the key is the field name and the value is the field value. However, there are some exceptions you should be aware of.
If the format is front matter, the body field is saved outside of the front matter block, as briefly explained in the Decap CMS document:
---
title: My Post
date: 2025-01-01
---
This is the body of my post.
instead of
---
title: My Post
date: 2025-01-01
body: This is the body of my post.
---
If there is only the body field, the front matter block is omitted altogether:
This is the body of my post.
However, this doesn’t apply when i18n is enabled with the single_file structure. In this case, the body field is saved part of key-value pairs under each locale in the front matter block:
---
en:
title: My Post
date: 2025-01-01
body: This is the body of my post.
fr:
title: Mon article
date: 2025-01-01
body: C’est le corps de mon article.
---
There are two exceptional cases for the List widget:
field (singular) option is used, the name property is omitted from the output. It will be saved as a simple list of values:images:
- https://example.com/image1.jpg
- https://example.com/image2.jpg
instead of an array of objects:images:
- image: https://example.com/image1.jpg
- image: https://example.com/image2.jpg
This is not mentioned in the Decap CMS document, but it’s a known behaviour. If you expect the latter, you can use the fields (plural) option to define a single field:- name: images
label: Images
widget: list
fields:
- { name: image, label: Image, widget: image }
root option is set to true, the List field is saved as a top-level list without a field name:- name: John Doe
id: 12345
- name: Jane Smith
id: 67890
instead ofmembers:
- name: John Doe
id: 12345
- name: Jane Smith
id: 67890
You may already have a CI/CD tool set up on your Git repository to automatically deploy changes to production. Occasionally, you make a lot of changes to your content to quickly reach the CI/CD provider’s (free) build limits, or you just don’t want to see builds triggered for every single small change.
With Sveltia CMS, you can disable automatic deployments by default and manually trigger deployments at your convenience. This is done by adding the [skip ci] prefix to commit messages, the convention supported by GitHub Actions, GitLab CI/CD, CircleCI, Travis CI, Netlify, Cloudflare Pages and others. Here are the steps to use it:
skip_ci property to your backend configuration with a value of true:backend:
name: github
repo: owner/repo
branch: main
skip_ci: true
[skip ci] is automatically added to each commit message. However, deletions are always committed without the prefix to avoid unexpected data retention on your site.[skip ci].If you set skip_ci to false, the behaviour is reversed. CI/CD will be triggered by default, while you have an option to Save without Publishing that adds [skip ci] only to the associated commit.
Gotcha: Unpublished entries and assets are not drafts. Once committed to your repository, those changes can be deployed any time another commit is pushed without [skip ci], or when a manual deployment is triggered.
If the skip_ci property is defined, you can manually trigger a deployment by clicking the Publish Changes button on the application header. To use this feature:
repository_dispatch event with the sveltia-cms-publish event type. Update your build workflow to receive this event:on:
push:
branches: [$default-branch]
repository_dispatch:
types: [sveltia-cms-publish]
If your site adopts Content Security Policy (CSP), use the following policy for Sveltia CMS, or some features may not work.
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' blob: data:;
media-src blob:;
frame-src blob:;
script-src 'self' https://unpkg.com;
connect-src 'self' blob: data: https://unpkg.com;
(UNPKG is used not only to download the CMS script bundle, but also to check for the latest version and retrieve additional dependencies such as PDF.js and Prism language definitions)
Then, add the following origins depending on your Git backend and enabled integrations.
img-srchttps://*.githubusercontent.com
connect-srchttps://api.github.com https://www.githubstatus.com
img-srchttps://gitlab.com https://secure.gravatar.com
connect-srchttps://gitlab.com https://status-api.hostedstatus.com
img-srchttps://gitea.com
connect-srchttps://gitea.com
img-srchttps://*.openstreetmap.org
connect-srchttps://*.openstreetmap.org
img-srchttps://res.cloudinary.com
or a custom domain if configuredframe-srchttps://console.cloudinary.com
img-srchttps://*.ucarecd.net https://ucarecdn.com
or a custom domain if configuredconnect-srchttps://upload.uploadcare.com https://api.uploadcare.com
img-srchttps://images.pexels.com
connect-srchttps://images.pexels.com https://api.pexels.com
img-srchttps://pixabay.com
connect-srchttps://pixabay.com
img-srchttps://images.unsplash.com
connect-srchttps://images.unsplash.com https://api.unsplash.com
connect-srchttps://translation.googleapis.com
connect-srchttps://api.anthropic.com
connect-srchttps://api.openai.com
frame-srchttps://www.youtube-nocookie.com
If you choose to disable automatic deployments and have configured a webhook URL, you may need to add the origin to the connect-src directive. For example,
connect-srchttps://api.netlify.com
connect-srchttps://api.cloudflare.com
If you have image field(s) and expect that images will be inserted as URLs, you may want to allow any source using a wildcard instead of specifying individual origins:
img-src 'self' blob: data: https://*;
A Release Notes link will now appear under the Account menu with the current application version.
While we don’t have dedicated developer/user support resources, you can post quick questions on the Discussions page of our GitHub repository. Feedback is also welcome, but please check the Compatibility and Roadmap sections of this README before starting a new discussion — your idea may already be covered.
Join us on Discord or ping us on Bluesky for a casual chat.
[!IMPORTANT] As described throughout this README, Sveltia CMS is specifically designed as a replacement for Netlify/Decap CMS. At this point, we assume that most developers and users are moving from the other product. We are happy to help you migrate, but we cannot help you set up Sveltia CMS from scratch through our free support channels.
Planning to build a website with Sveltia CMS? Looking for professional support? Maintainer @kyoshino is available for hire depending on your needs. Feel free to reach out!
See Contributing to Sveltia CMS. Bug reports are highly encouraged.
As mentioned in the Project Status section, our goal is to solve most of the Netlify/Decap CMS issues over the course of this project. We also have lots of ideas to make Sveltia CMS a great product.
We cannot promise any specific release dates, but here is a rough roadmap for the next few years.
Due early 2026
See also the 1.0 RC and 1.0 milestones for a list of issues planned for v1.0.
Due mid-2026
Due late 2026
Sveltia CMS is not a service but a client-side application that runs in your web browser. You don’t need an account to use the app, but you do need to authenticate with your Git hosting provider to read and write remote data. All content is stored in your Git repository. No data is sent to any server operated by us.
Depending on your site configuration, you will need to use an OAuth application hosted by yourself or a third party, such as Netlify or Cloudflare, to retrieve an access token from GitHub. Alternatively, you can provide an access token directly on the CMS’s sign-in page. In any case, your token is stored in your browser’s local storage, and subsequent API requests are made directly between your browser and the Git hosting provider.
The CMS also integrates with various third-party services, including stock photo providers and translation services. These are “bring your own key” (BYOK) features that are entirely optional. You provide your own API keys for these services, which are stored in your browser’s local storage, and API requests are then made directly between your browser and the relevant service providers.
As we don’t collect any analytics data either, we don’t have a privacy policy. For third-party services, please refer to their respective privacy policies.
This software is provided “as is” without any express or implied warranty. We are not obligated to provide any support for the application. This product is not affiliated with or endorsed by Netlify, Decap CMS or any other integrated services. All product names, logos, and brands are the property of their respective owners.
This project would not have been possible without the open source Netlify CMS project. We are grateful to the maintainers for their hard work over the years. We would also like to thank the Sveltia CMS user community for their valuable feedback and ongoing support, which has helped us to identify issues and improve the product.
[^1]: Netlify/Decap CMS #2557
[^2]: Netlify/Decap CMS #2039, #3267, #7084
[^3]: Netlify/Decap CMS #1040
[^4]: Netlify/Decap CMS #3671
[^6]: Netlify/Decap CMS #3240
[^7]: Netlify/Decap CMS #4386, #4888, #7332, #7514
[^8]: Netlify/Decap CMS #2579
[^9]: Netlify/Decap CMS #3505, #4211, #5439
[^10]: Netlify/Decap CMS #341, #1167
[^11]: Netlify/Decap CMS #1382, #6994
[^12]: Netlify/Decap CMS #1975, #3712
[^13]: Netlify/Decap CMS #5112, #5653
[^14]: Netlify/Decap CMS #4635, #5920, #6410, #6827, #6924
[^15]: Netlify/Decap CMS #6932
[^16]: Netlify/Decap CMS #2103, #2790, #7302
[^17]: Netlify/Decap CMS #1333, #4216
[^18]: Netlify/Decap CMS #441, #1277, #1339, #2500, #2833, #2984, #3852, #7083
[^19]: Netlify/Decap CMS #5910
[^20]: Netlify/Decap CMS #4563
[^21]: Netlify/Decap CMS #4781
[^22]: Netlify/Decap CMS #3615, #4069, #5097, #6642
[^23]: Netlify/Decap CMS #2, #2319, #6216, #6933
[^24]: Netlify/Decap CMS #6831
[^25]: Netlify/Decap CMS #526, #6987
[^26]: Netlify/Decap CMS #3285, #7030, #7067, #7217
[^27]: Netlify/Decap CMS #4564, #5617, #5815
[^28]: Netlify/Decap CMS #2677, #6836
[^29]: Netlify/Decap CMS #3750, #4783, #6801, #6806
[^30]: Netlify/Decap CMS #565, #6733
[^31]: Netlify/Decap CMS #1045
[^32]: Netlify/Decap CMS #302, #5549
[^33]: Netlify/Decap CMS #542, #4532, #6513, #7295, #7567
[^34]: Netlify/Decap CMS #2138, #2343, #4367, #5932
[^35]: Netlify/Decap CMS #7086
[^36]: Netlify/Decap CMS #6325
[^37]: Netlify/Decap CMS #1481, #7398
[^38]: Netlify/Decap CMS #1984
[^39]: Netlify/Decap CMS #946, #1970
[^40]: Netlify/Decap CMS #5630
[^41]: Netlify/Decap CMS #7011
[^42]: Netlify/Decap CMS #2307
[^43]: Netlify/Decap CMS #4387, #5381
[^44]: Netlify/Decap CMS #2613
[^45]: Netlify/Decap CMS #1424
[^46]: Netlify/Decap CMS #4726
[^47]: Netlify/Decap CMS #2370, #5596
[^48]: Netlify/Decap CMS #5569, #6754
[^49]: Netlify/Decap CMS #5752
[^50]: Netlify/Decap CMS #4646, #7167
[^51]: Netlify/Decap CMS #6731
[^52]: Netlify/Decap CMS #6970, #7147
[^53]: Netlify/Decap CMS #512, #5673, #6707, #7501
[^54]: Netlify/Decap CMS #1347, #1559, #4629, #4837, #6287, #6826 — Decap CMS 3.0 updated the Slate editor in an attempt to fix the problems, but the IME issues remain unresolved when using a mobile/tablet browser.
[^55]: Netlify/Decap CMS #4480, #5122, #6353
[^56]: Netlify/Decap CMS #6515
[^57]: Netlify/Decap CMS #328, #1290
[^58]: Netlify/Decap CMS #5125
[^59]: Netlify/Decap CMS #1654
[^60]: Netlify/Decap CMS #283, #386
[^61]: Netlify/Decap CMS #1489, #5838
[^62]: Netlify/Decap CMS #7192
[^63]: Netlify/Decap CMS #4877
[^64]: Netlify/Decap CMS #3853
[^65]: Netlify/Decap CMS #6034
[^66]: Netlify/Decap CMS #3353
[^67]: Netlify/Decap CMS #7077
[^68]: Netlify/Decap CMS #6978
[^69]: Netlify/Decap CMS #4350
[^70]: Netlify/Decap CMS #6482
[^71]: Netlify/Decap CMS #6999, #7000, #7001, #7152, #7220, #7283, #7316, #7429, #7465, #7500, #7552
[^72]: Netlify/Decap CMS #7047
[^73]: Netlify/Decap CMS #6993, #7123, #7127, #7128, #7237, #7251, #7361, #7391, #7393, #7470, #7475, #7480, #7503, #7504, #7524, #7531, #7535, #7553, #7561, #7584, #7591, #7609, #7628
[^74]: Netlify/Decap CMS #4209
[^75]: Netlify/Decap CMS #5472
[^76]: Netlify/Decap CMS #4738
[^77]: Netlify/Decap CMS #2009, #2293, #3415, #3952, #6563
[^78]: Netlify/Decap CMS #2294, #3046, #4363, #4520, #5806
[^79]: Netlify/Decap CMS #5726
[^80]: Netlify/Decap CMS #5493, #6600
[^81]: Netlify/Decap CMS #4645
[^82]: Netlify/Decap CMS #5593, #6500
[^83]: Netlify/Decap CMS #6508
[^84]: Netlify/Decap CMS #7142, #7276
[^85]: Netlify/Decap CMS #5055, #5470, #6956, #6989
[^86]: Netlify/Decap CMS #1609, #3557, #5253, #6760, #6901 — Looks like the fix for #215 was incomplete or regressed at some point.
[^87]: Netlify/Decap CMS #5280
[^88]: Netlify/Decap CMS #1267
[^89]: Netlify/Decap CMS #4255
[^90]: Netlify/Decap CMS #725
[^91]: Netlify/Decap CMS #4954
[^92]: Netlify/Decap CMS #1466
[^93]: Netlify/Decap CMS #1000
[^94]: Netlify/Decap CMS #5487
[^95]: Netlify/Decap CMS #4417
[^96]: Netlify/Decap CMS #962
[^97]: Netlify/Decap CMS #4288
[^98]: Netlify/Decap CMS #3856
[^99]: Netlify/Decap CMS #1685
[^100]: Netlify/Decap CMS #4147
[^101]: Netlify/Decap CMS #5969
[^102]: Netlify/Decap CMS #1270
[^103]: Netlify/Decap CMS #6307
[^104]: Netlify/Decap CMS #450, #2122, #6819
[^105]: Netlify/Decap CMS #5701
[^106]: Netlify/Decap CMS #2822
[^107]: Netlify/Decap CMS #332, #683, #999, #1456, #4175, #4818, #5688, #6828, #6829, #6862, #7023
[^108]: Netlify/Decap CMS #6879
[^109]: Netlify/Decap CMS #7197
[^110]: Netlify/Decap CMS #4637, #5198
[^111]: Netlify/Decap CMS #7190, #7218, #7392
[^112]: Netlify/Decap CMS #5815, #6522, #6532, #6588, #6617, #6640, #6663, #6695, #6697, #6764, #6765, #6835, #6983, #7205, #7450, #7453, #7572, #7602
[^113]: Netlify/Decap CMS #5656, #5837, #5972, #6476, #6516, #6930, #7080, #7105, #7106, #7119, #7176, #7194, #7244, #7278, #7301, #7342, #7348, #7354, #7376, #7408, #7412, #7413, #7422, #7427, #7434, #7438, #7454, #7464, #7471, #7485, #7499, #7515, #7564, #7571, #7574, #7580, #7583, #7589, #7593, #7595, #7601, #7610, #7614, #7620, #7621, #7622, #7631, #7643, #7644, #7648 — These removeChild crashes are common in React apps, likely caused by a browser extension or Google Translate.
[^114]: Netlify/Decap CMS #5029, #5048
[^115]: Netlify/Decap CMS #7172
[^116]: Netlify/Decap CMS #3431
[^117]: Netlify/Decap CMS #3562, #6215, #6605, #6355, #7479
[^118]: Netlify/Decap CMS #7267
[^119]: Netlify/Decap CMS #5640, #6444
[^120]: Netlify/Decap CMS #2727, #4884, #6908
[^121]: Netlify/Decap CMS #7262
[^122]: Netlify/Decap CMS #1776, #2064, #7158, #7259
[^123]: Netlify/Decap CMS #961, #5489
[^124]: Netlify/Decap CMS #991, #4488, #7233
[^125]: Netlify/Decap CMS #475, #5469
[^126]: Netlify/Decap CMS #7279
[^127]: Netlify/Decap CMS #2289, #4518
[^128]: Netlify/Decap CMS #7092
[^129]: Netlify/Decap CMS #4961, #4979, #5545, #5778, #6279, #6464, #6810, #6922, #7118, #7293, #7630 — A comment on one of the issues says the crash was due to Google Translate. Sveltia CMS has turned off Google Translate on the admin page.
[^130]: Netlify/Decap CMS #6571, #7539
[^131]: Netlify/Decap CMS #4429
[^132]: Netlify/Decap CMS #6816
[^133]: Netlify/Decap CMS #445
[^134]: Netlify/Decap CMS #5548
[^135]: Netlify/Decap CMS #2133
[^136]: Netlify/Decap CMS #7085
[^137]: Netlify/Decap CMS #4092
[^138]: Netlify/Decap CMS #4841
[^139]: Netlify/Decap CMS #6202
[^140]: Netlify/Decap CMS #5444
[^141]: Netlify/Decap CMS #3723, #6990
[^142]: Netlify/Decap CMS #7124
[^143]: Netlify/Decap CMS #1341
[^144]: Netlify/Decap CMS #3284
[^145]: Netlify/Decap CMS #4733
[^146]: Netlify/Decap CMS #2524
[^147]: Netlify/Decap CMS #3583
[^148]: Netlify/Decap CMS #531, #621, #1282, #1877, #2514, #2737
[^149]: Netlify/Decap CMS #13 — The issue appears to have been closed without a fix being available.
[^150]: Netlify/Decap CMS #7319
[^151]: Netlify/Decap CMS #7328
[^152]: Netlify/Decap CMS #2491
[^153]: Netlify/Decap CMS #7347
[^154]: Netlify/Decap CMS #1449, #1988, #5552
[^155]: Netlify/Decap CMS #5870
[^156]: Netlify/Decap CMS #995, #2017, #7120, #7186
[^157]: Netlify/Decap CMS #2007, #2848
[^158]: Netlify/Decap CMS #6107
[^159]: Netlify/Decap CMS #3796
[^160]: Netlify/Decap CMS #3291
[^161]: Netlify/Decap CMS #1274
[^162]: Netlify/Decap CMS #2380
[^163]: Netlify/Decap CMS #7322
[^164]: Netlify/Decap CMS #756 — The Expand All and Collapse All buttons cannot be found in the current version of Decap CMS.
[^165]: Netlify/Decap CMS #7143
[^166]: Netlify/Decap CMS #277, #1500
[^167]: Netlify/Decap CMS #263, #2034
[^168]: Netlify/Decap CMS #1948
[^169]: Netlify/Decap CMS #7364
[^170]: Netlify/Decap CMS #7371
[^171]: Netlify/Decap CMS #4754
[^172]: Netlify/Decap CMS #3715
[^173]: Netlify/Decap CMS #5317
[^174]: Netlify/Decap CMS #6616
[^175]: Netlify/Decap CMS #5376, #7203, #7380
[^176]: Netlify/Decap CMS #6427
[^177]: Netlify/Decap CMS #2673, #5315, #6499, #6544, #6551, #6679, #6773, #6883, #7363, #7365 — This problem occurs every time a new major version of React is released.
[^178]: Netlify/Decap CMS #2536
[^179]: Netlify/Decap CMS #1891
[^180]: Netlify/Decap CMS #7399
[^181]: Netlify/Decap CMS #6254
[^182]: Netlify/Decap CMS #4416, #7400
[^183]: Netlify/Decap CMS #1275
[^184]: Netlify/Decap CMS #377
[^185]: Netlify/Decap CMS #6203, #7417, #7451
[^186]: Netlify/Decap CMS #2368, #3454, #3585, #3651, #3885, #3962, #4037, #4143, #6585, #6664, #6665, #6739, #7243, #7379, #7469
[^187]: Netlify/Decap CMS #1244
[^189]: Netlify/Decap CMS #7431
[^190]: Netlify/Decap CMS #4987
[^191]: Netlify/Decap CMS #5970
[^192]: Netlify/Decap CMS #6527
[^193]: Netlify/Decap CMS #6800
[^194]: Netlify/Decap CMS #7157
[^195]: Netlify/Decap CMS #5901
[^196]: Netlify/Decap CMS #3057, #3260 — We use Svelte though.
[^197]: Netlify/Decap CMS #3457, #3624, #6713
[^198]: Netlify/Decap CMS #7442
[^199]: Netlify/Decap CMS #5419, #7107
[^200]: Netlify/Decap CMS #1322, #6442
[^201]: Netlify/Decap CMS #7381
[^202]: Netlify/Decap CMS #7458
[^203]: Netlify/Decap CMS #7360, #7462
[^204]: Netlify/Decap CMS #7240, #7428
[^205]: Netlify/Decap CMS #3257
[^206]: Netlify/Decap CMS #3258
[^207]: Netlify/Decap CMS #3259
[^208]: Netlify/Decap CMS #3261
[^209]: Netlify/Decap CMS #3262
[^210]: Netlify/Decap CMS #3296
[^211]: Netlify/Decap CMS #3263
[^212]: Netlify/Decap CMS #3264
[^213]: Netlify/Decap CMS #3265
[^214]: Netlify/Decap CMS #3266
[^215]: Netlify/Decap CMS #7241
[^216]: Netlify/Decap CMS #1345
[^217]: Netlify/Decap CMS #5467, #6505
[^218]: Netlify/Decap CMS #978
[^219]: Netlify/Decap CMS #3018
[^220]: Netlify/Decap CMS #2153
[^221]: Netlify/Decap CMS #3421
[^222]: Netlify/Decap CMS #7281 — The issue was closed, but the attached PR is not yet merged.
[^223]: Netlify/Decap CMS #7483
[^224]: Netlify/Decap CMS #7352, #7581
[^226]: Netlify/Decap CMS #7031, #7540, #7597
[^227]: Netlify/Decap CMS #6794
[^228]: Netlify/Decap CMS #6762
[^229]: Netlify/Decap CMS #6180
[^230]: Netlify/Decap CMS #7486
[^231]: Netlify/Decap CMS #3704
[^232]: Netlify/Decap CMS #7457
[^233]: Netlify/Decap CMS #427, #535, #1284, #3846
[^234]: Netlify/Decap CMS #2243, #4965 — Rather than relying on a third-party library, we built our own asset browser that integrates more seamlessly with the rest of the CMS.
[^235]: Netlify/Decap CMS #6905 — We use Lexical created by Facebook (Meta).
[^236]: Netlify/Decap CMS #7507
[^237]: Netlify/Decap CMS #7375
[^238]: Netlify/Decap CMS #1502
[^239]: Netlify/Decap CMS #213, #1032, #3244, #6730, #7459
[^240]: Netlify/Decap CMS #2113, #3240
[^241]: Netlify/Decap CMS #857
[^242]: Netlify/Decap CMS #192
[^243]: Netlify/Decap CMS #717, #5750, #6895
[^244]: Netlify/Decap CMS #1206
[^245]: Netlify/Decap CMS #1769
[^246]: Netlify/Decap CMS #1074, #1693, #3803
[^247]: Netlify/Decap CMS #1794, #2966
[^248]: Netlify/Decap CMS #836, #3524
[^249]: Netlify/Decap CMS #4208
[^250]: Netlify/Decap CMS #6609, #6802, #6824, #6832, #6848, #6851, #7287, #7522
[^251]: Netlify/Decap CMS #6965, #7445
[^252]: Netlify/Decap CMS #6629
[^253]: Netlify/Decap CMS #6635, #7006, #7311
[^254]: Netlify/Decap CMS #7532
[^255]: Netlify/Decap CMS #7355
[^256]: Netlify/Decap CMS #2845
[^257]: Netlify/Decap CMS #4353, #7513, #7544
[^258]: Netlify/Decap CMS #663, #6729, #7466, #7545
[^259]: Netlify/Decap CMS #7546
[^260]: Netlify/Decap CMS #5867, #6159, #7016, #7306, #7554
[^261]: Netlify/Decap CMS #942
[^262]: Netlify/Decap CMS #1727
[^263]: Netlify/Decap CMS #1342
[^264]: Netlify/Decap CMS #7557
[^265]: Netlify/Decap CMS #6215
[^266]: Netlify/Decap CMS #3821, #4369
[^267]: Netlify/Decap CMS #5065 — The issue was closed, but it’s still not working as expected in Decap CMS.
[^268]: Netlify/Decap CMS #4945
[^269]: Netlify/Decap CMS #7568
[^270]: Netlify/Decap CMS #292 — It cannot be found in the current version of Decap CMS.
[^271]: Netlify/Decap CMS #1046
[^272]: Netlify/Decap CMS #4702
[^273]: Netlify/Decap CMS #919, #936, #1286, #1288, #1400
[^274]: Netlify/Decap CMS #549
[^275]: Netlify/Decap CMS #4762
[^277]: Netlify/Decap CMS #86
[^278]: Netlify/Decap CMS #7575
[^279]: Netlify/Decap CMS #1390, #4912, #6986
[^280]: Netlify/Decap CMS #3490, #3682, #4669, #4895, #4976, #5766, #6501, #6785, #7315
[^281]: Netlify/Decap CMS #4892 — We cannot reproduce the crash, but we do see the error message in the console.
[^282]: Netlify/Decap CMS #7579
[^283]: Netlify/Decap CMS #713
[^284]: Netlify/Decap CMS #7585
[^285]: Netlify/Decap CMS #6597
[^286]: Netlify/Decap CMS #2367
[^287]: Netlify/Decap CMS #1069
[^289]: Netlify/Decap CMS #2183
[^290]: Netlify/Decap CMS #7611
[^291]: Netlify/Decap CMS #7612
[^292]: Netlify/Decap CMS #7518
[^293]: Netlify/Decap CMS #7616
[^294]: Netlify/Decap CMS #7576, #7587
[^295]: Netlify/Decap CMS #6243
[^296]: Netlify/Decap CMS #7638
[^297]: Netlify/Decap CMS #2001
[^298]: Netlify/Decap CMS #7640
[^299]: Netlify/Decap CMS #5812
[^300]: Netlify/Decap CMS #7257