Migrating from Pages CMS
Pages CMS is a Git-based CMS for static sites that was inspired by Netlify CMS. Its content model is close to that of Sveltia CMS: content is stored as Markdown, YAML, JSON or TOML files in a Git repository, and the content structure is defined in a configuration file. This makes the migration relatively straightforward, as existing content files can be kept as they are in most cases.
This guide explains the differences between the two platforms and how to convert a Pages CMS configuration file to the Sveltia CMS format. It’s based on Pages CMS 2.x.
Stable Version Not Yet Available
Sveltia CMS is still in beta. Although it’s already being used in production by many users, there might still be breaking changes before the stable 1.0 release. We recommend keeping an eye on the release information for any updates.
Examples
See the following examples of sites that have been migrated from Pages CMS to see how other users have successfully transitioned to Sveltia CMS.
Key Differences
Architecture
Pages CMS is a Next.js application. Users sign in to the hosted app at app.pagescms.org or to a self-hosted instance, which accesses repositories through a GitHub App and keeps a cache in a PostgreSQL database.
Sveltia CMS is a single-page application that runs entirely in the browser and talks to the Git hosting service directly. It’s installed on the site itself, typically at /admin/, and there is no hosted app, database or GitHub App to set up. See Architecture for details.
As a result, the migration involves the following changes:
- Installation: Instead of installing a GitHub App, add an
admin/index.htmlfile to the site. See the Start Guide. - Configuration: The configuration file is
admin/config.ymlinstead of.pages.ymlat the root of the repository. The structure is different, so it must be converted. See below. - Authentication: Users sign in with their GitHub account using an OAuth client or a personal access token. See GitHub Backend for the available methods.
- Git hosting services: Pages CMS supports GitHub only, while Sveltia CMS also supports GitLab, Gitea and Forgejo. This gives you the option to move the repository to another service later.
Collaborators
Pages CMS allows repository owners to invite collaborators by email, so people without a GitHub account can edit content through the GitHub App. Sveltia CMS doesn’t have an equivalent feature yet, as it requires each user to have an account with write access to the repository on the Git hosting service. Invite your collaborators to the repository on GitHub instead. See Invite Team Members.
We plan to support user management and roles with Sveltia CMS Additions, our free server-side component.
Features Not Available in Sveltia CMS
The following Pages CMS features have no direct equivalent in Sveltia CMS:
- Branch switching: Pages CMS reads
.pages.ymlfrom the branch being edited and lets users switch branches. Sveltia CMS works on the single branch specified with thebranchbackend option. The Editorial Workflow can be used to review changes in separate branches before publishing them. - Actions: Pages CMS can run GitHub Actions workflows from buttons with custom inputs. When automatic deployments are disabled, Sveltia CMS can trigger a deployment with the Publish Changes button, which sends a
repository_dispatchevent to GitHub Actions or calls a webhook, but it doesn’t support other workflows or inputs. - Commit identity: Pages CMS can commit as the GitHub App. Sveltia CMS always commits as the signed-in user.
- Raw and datagrid editors: Pages CMS provides a plain text editor for files without fields and a spreadsheet-like editor for CSV files. Sveltia CMS can manage such files with the
rawformat and a single Code or Text field namedbody, but there is no table editor. - Rich text in HTML: The
format: htmloption of therich-textfield type is not supported yet, as the RichText field type currently outputs Markdown only. HTML output is planned for a future release. - Random filenames: The
rename: randommedia option is not supported. Therename: safeoption is supported asslugify_filename. - Nested sidebar groups: The
groupcontent type is not supported. Use dividers to separate the collection list, or put related files into one file collection.
On the other hand, Sveltia CMS offers many features that are not available in Pages CMS, including internationalization, Editorial Workflow, entry previews, external media storage, image optimization, local development without a server and GitLab, Gitea and Forgejo support. See Features for the full list.
Converting the Configuration
Pages CMS and Sveltia CMS use different option names, but most of the concepts map directly to each other. This section lists the equivalents. You can also ask your AI assistant to convert the .pages.yml file using this guide; see Working with AI for tools that help it write a valid configuration.
Example
Here is a typical Pages CMS configuration:
media:
input: public/images
output: /images
content:
- name: posts
label: Posts
type: collection
path: content/posts
filename: '{year}-{month}-{day}-{primary}.md'
view:
fields: [title, date]
sort: [date, title]
default: { sort: date, order: desc }
fields:
- { name: title, label: Title, type: string, required: true }
- { name: date, label: Date, type: date }
- { name: draft, label: Draft, type: boolean }
- { name: tags, label: Tags, type: string, list: true }
- { name: cover, label: Cover Image, type: image }
- { name: body, label: Body, type: rich-text }
- name: settings
label: Site Settings
type: file
path: data/settings.json
fields:
- { name: title, label: Site Title, type: string }
- { name: description, label: Description, type: text }And here is the equivalent Sveltia CMS configuration:
backend:
name: github
repo: owner/repo
branch: main
media_folder: /public/images
public_folder: /images
collections:
- name: posts
label: Posts
folder: /content/posts
slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
summary: '{{title}} ({{date}})'
sortable_fields:
fields: [date, title]
default: { field: date, direction: descending }
fields:
- { name: title, label: Title }
- { name: date, label: Date, widget: datetime, type: date, default: '{{now}}', required: false }
- { name: draft, label: Draft, widget: boolean, required: false }
- { name: tags, label: Tags, widget: list, required: false }
- { name: cover, label: Cover Image, widget: image, required: false }
- { name: body, label: Body, widget: richtext, required: false }
- name: settings
label: Site Settings
files:
- name: settings
label: Site Settings
file: /data/settings.json
fields:
- { name: title, label: Site Title, required: false }
- { name: description, label: Description, widget: text, required: false }Note the following differences:
- The
backendoption is required to tell Sveltia CMS where the content is stored. - Paths can start with a slash, and entry filenames are defined with the
slugoption without the extension. - The
typefield option becomeswidget, which defaults tostring. - Fields are required by default in Sveltia CMS, while they are optional in Pages CMS. Add
required: falseto fields that were notrequired: true.
Media
The media option is converted as follows. See Internal Media Storage for details.
| Pages CMS | Sveltia CMS |
|---|---|
media: media (string) | media_folder: /media and public_folder: /media |
input | media_folder |
output | public_folder |
extensions, categories | The accept option of each File/Image field, e.g. accept: .pdf,.docx or image/* |
rename: true or rename: safe | slugify_filename: true under media_libraries.default.config |
rename: random | Not supported |
| Multiple named media sources | Asset collections |
media and path options of a field | Field-level media_folder and public_folder |
Content Types
A Pages CMS collection is an entry collection, and a file is a file in a file collection or a singleton. Singletons are a good fit if your Pages CMS configuration has many standalone files.
| Pages CMS | Sveltia CMS |
|---|---|
name, label | name, label |
path (collection) | folder |
path (file) | file in a files item |
filename | slug for the template, plus extension |
filename.field: false | slug.editable: false |
filename.field: create | slug.editable: [create] |
filename.field: true | slug.editable: true (default) |
format | format. The values yaml-frontmatter, json-frontmatter, toml-frontmatter, yaml, json, toml and raw are the same. Use raw for code and datagrid |
delimiters | frontmatter_delimiter |
subfolders: true | Nested collections or the path option |
list: true on a file | A top-level List field, or a single-file collection for a JSON file |
exclude | No filename-based option. Use filter to filter entries by field value |
operations.create, operations.delete | create, delete |
operations.rename | slug.editable |
group | Not supported. See above |
Filename Templates
The filename template tags are converted to slug template tags as follows:
| Pages CMS | Sveltia CMS |
|---|---|
{primary}, {slug} | {{slug}}, which uses the title field or the field specified with identifier_field. Pages CMS falls back to the first field if there is no title field, so set identifier_field in that case |
{year}, {month}, {day}, {hour}, {minute}, {second} | {{year}}, {{month}}, {{day}}, {{hour}}, {{minute}}, {{second}} |
{fields.name}, {name} | {{fields.name}}, {{name}} |
The file extension is not part of the template. For example, filename: '{year}-{month}-{day}-{primary}.md' becomes slug: '{{year}}-{{month}}-{{day}}-{{slug}}', and the md extension is used by default.
Pages CMS uses the default filename {year}-{month}-{day}-{primary}.md when the option is omitted, while Sveltia CMS uses {{slug}}. Set the slug option explicitly to keep the same naming convention for new entries.
The date and time tags use the local time in Pages CMS and UTC in Sveltia CMS by default. Set the timezone global slug option to local to use the local time.
Pages CMS converts field values in filenames to lowercase ASCII letters, numbers and hyphens, while Sveltia CMS keeps Unicode characters by default. Set the encoding global slug option to ascii and clean_accents to true for similar filenames.
Views
The view options are converted as follows. See Entry Listings and Entry Views for details.
| Pages CMS | Sveltia CMS |
|---|---|
view.primary | identifier_field |
view.fields | summary template, e.g. '{{title}} ({{date}})' |
view.sort | sortable_fields |
view.default.sort, view.default.order | sortable_fields.default.field, sortable_fields.default.direction (ascending or descending) |
view.search | Not needed, as all entries are searchable |
view.layout: tree, view.node | Nested collections |
Fields
The common field options are converted as follows. See Fields for details.
| Pages CMS | Sveltia CMS |
|---|---|
name, label | name, label |
type | widget |
description | hint |
required (default: false) | required (default: true) |
readonly | readonly |
pattern: '^[a-z]+$' | pattern: ['^[a-z]+$', 'Error message']. See pattern |
pattern: { regex, message } | pattern: [regex, message] |
hidden: true | widget: hidden with a default value. See Hidden |
default | default |
list: true | A List field. See below |
component | YAML anchors and aliases. See below |
The field types are converted as follows:
| Pages CMS | Sveltia CMS |
|---|---|
string | string (default) |
text | text |
rich-text | richtext (Markdown only) |
code | code |
number | number |
boolean | boolean |
date | datetime |
select | select |
reference | relation |
image | image |
file | file |
object | object |
block | list or object with the types option |
uuid | uuid |
| Custom field types | Custom field types |
The minlength, maxlength, min, max and step options work the same way where supported. Other type-specific options are described below.
Code
The Code field in Sveltia CMS saves an object containing the code and the language by default. To save the code as a plain string, as in Pages CMS, set output_code_only: true. The options.format option becomes default_language.
- name: snippet
label: Snippet
widget: code
default_language: javascript
output_code_only: true
allow_language_selection: falseNumber
The Number field in Sveltia CMS accepts integers only by default. Set value_type: float to allow decimal numbers.
Date
The date field type is converted to the DateTime field type:
- Without
options.time, addtype: date. The value is saved asYYYY-MM-DD, as in Pages CMS. - With
options.time: true, the value is saved in ISO 8601 format with seconds, such as2025-08-15T14:30:00. Pages CMS saves2025-08-15T14:30by default. Use theformatoption if your site needs the same format. - The
options.formatoption becomesformat, but Pages CMS uses date-fns tokens while Sveltia CMS uses Day.js tokens. For example,yyyy-MM-dd'T'HH:mmbecomesYYYY-MM-DD[T]HH:mm. - Pages CMS sets new entries to the current date by default. Add
default: '{{now}}'to do the same.
- name: date
label: Date
widget: datetime
type: date
default: '{{now}}'Select
The options.values option becomes options. Objects with name and label properties become objects with value and label properties. The multiple, min and max options work the same way. The placeholder option is not supported.
- name: status
label: Status
widget: select
options:
- { label: Draft, value: draft }
- { label: Published, value: published }Reference
The reference field type is converted to the Relation field type:
| Pages CMS | Sveltia CMS |
|---|---|
options.collection | collection |
options.value | value_field |
options.label | display_fields |
options.search | search_fields, as an array |
options.multiple, options.min, options.max | multiple, min, max |
The template tags are similar, but they are enclosed in double curly braces, e.g. {{fields.name}}.
Pages CMS saves the full path of the referenced file by default, such as content/authors/jane.md, while Sveltia CMS saves the entry slug, such as jane. If your existing content contains file paths, set value_field to a field that has the same value, or update the content to use slugs. The {primary} tag becomes the name of the identifier field, e.g. {{title}}.
Image and File
The options.multiple option becomes multiple, and options.multiple.max becomes max. The options.extensions and options.categories options become the accept option, e.g. accept: .pdf or accept: image/*. The options.media and options.path options become field-level media_folder and public_folder.
UUID
The options.editable: true option becomes readonly: false, as the UUID field is read-only by default.
Lists
A field with list: true becomes a List field. For a list of strings, use a List field without subfields. For a list of other types, specify the subfield with the field option. The list.min and list.max options become min and max.
# Pages CMS
- { name: tags, label: Tags, type: string, list: true }
- { name: photos, label: Photos, type: image, list: { max: 10 } }
- name: links
label: Links
type: object
list: { collapsible: { collapsed: true, summary: '{fields.title}' } }
fields:
- { name: title, label: Title, type: string }
- { name: url, label: URL, type: string }
# Sveltia CMS
- { name: tags, label: Tags, widget: list }
- name: photos
label: Photos
widget: list
max: 10
field: { name: photo, label: Photo, widget: image }
- name: links
label: Links
widget: list
collapsed: true
summary: '{{title}}'
fields:
- { name: title, label: Title }
- { name: url, label: URL }Note that a List field with the fields option, like links above, is an object list. See the List field documentation for the data output of each configuration.
Blocks
A block field with list: true becomes a List field with the types option. Each block becomes a type. The property that stores the block name is _block by default in Pages CMS and type in Sveltia CMS, so set the typeKey option to _block, or to the blockKey value if you have customized it.
# Pages CMS
- name: sections
label: Sections
type: block
list: true
blocks:
- name: hero
label: Hero
fields:
- { name: heading, type: string }
- name: text
label: Text
fields:
- { name: body, type: rich-text }
# Sveltia CMS
- name: sections
label: Sections
widget: list
typeKey: _block
types:
- name: hero
label: Hero
fields:
- { name: heading, label: Heading }
- name: text
label: Text
fields:
- { name: body, label: Body, widget: richtext }A block field without list: true becomes an Object field with the types option.
Components
Sveltia CMS doesn’t have a components option, but YAML anchors and aliases can be used to reuse field definitions. Define a field with an anchor (&name) once, then reference it with an alias (*name). The merge key (<<) allows you to override some options, like the component option in Pages CMS.
collections:
- name: pages
label: Pages
folder: /content/pages
fields:
- &seo
name: seo
label: SEO
widget: object
fields:
- { name: title, label: Title }
- { name: description, label: Description, widget: text }
- name: posts
label: Posts
folder: /content/posts
fields:
- <<: *seo
label: MetaSettings
| Pages CMS | Sveltia CMS |
|---|---|
settings.content.merge | Not needed. Sveltia CMS always keeps the properties that are not defined in the configuration, placing them after the configured fields. |
settings.commit.templates | commit_messages backend option. The template tags are different, e.g. {{collection}}, {{slug}} and {{path}} |
settings.commit.identity | Not supported. Commits are always made by the signed-in user |
settings.hide | Not needed, as there is no settings page |
Migration Steps
- Review the key differences above to make sure there are no blockers.
- Follow the Start Guide to add Sveltia CMS to your site, including the
admin/index.htmlfile and the backend configuration. - Convert
.pages.ymltoadmin/config.ymlusing the mapping above. Enable JSON schema validation in your code editor to catch mistakes early. - Test the configuration with the local development workflow. Open some existing entries and save them to make sure the data output is unchanged, as there might be differences in formatting. Use the output options to adjust the output if needed.
- Deploy the site and set up authentication for production.
- Invite your Pages CMS collaborators to the GitHub repository, and share the admin URL with them.
- Once everything works as expected, delete
.pages.ymlfrom the repository and uninstall the Pages CMS GitHub App from your account or organization.
If you encounter any issues during the migration, feel free to ask in our Discussions or report a bug.