# Introduction Source: https://pagescms.org/docs/ What Pages CMS is, who it is for, and how it works. ## What Pages CMS is [Pages CMS](https://pagescms.org) is an open-source CMS for static sites stored in GitHub repositories. It edits files in your repository directly. There is no separate CMS database for content. ## Why it exists Most static sites do not need a database-backed CMS. They already have: - content in files, - media in the repository, - Git history, - a deployment flow. The missing piece is usually the editing experience. Pages CMS gives teams a UI for editing content and media without asking every editor to learn Git. ## How it works 1. Add a `.pages.yml` file to the repository. 2. Define `content`, `media`, and any optional `components` or `settings`. 3. Sign in to Pages CMS. 4. Edit content in the UI. 5. Save changes back to GitHub. ## What Pages CMS does not do Pages CMS does not replace your site generator, deployment platform, or repository workflow. It only provides the editing layer on top of your existing Git-based project.
--- # Quick start Source: https://pagescms.org/docs/quick-start/ The fastest path from zero to a working Pages CMS setup. ## Quick start If you just want to try Pages CMS, use the hosted app. 1. Go to [app.pagescms.org](https://app.pagescms.org). 2. Sign in with GitHub. 3. Install the GitHub App on the account or organization that owns your repository. 4. Open the repository you want to edit. 5. Create `.pages.yml` when prompted. 6. Start editing. ## Minimal config Use this as a first working config: ```yaml media: media content: - name: pages label: Pages type: collection path: docs fields: - name: title type: string - name: body type: rich-text ``` This gives you: - one media folder at `media/`, - one editable collection at `docs/`, - a `title` field, - a rich-text `body` field. ## What to do next 1. Add more fields. 2. Configure media storage. 3. Adjust filenames and collection view.name ** | Internal media source name. Required when using an array (e.g. `"images"`).
label | UI label for the media source (e.g. `"Product images"`).
input * | Repository path where files are stored (e.g. `"src/media"`).
output * | Public path written into content (e.g. `"/media"`).
extensions | Allowed extensions (e.g. `["png", "webp"]`).
categories | Category-based extension sets. Values: `image`, `document`, `video`, `audio`, `compressed`, `code`, `font`, `spreadsheet`.
rename | Controls upload renaming. Use `false` to keep the original filename (default behavior), `true` or `safe` to slugify it, or `random` for a generated name.
commit | Per-media commit settings. [See `settings.commit`](#/docs/configuration/settings/) .
actions | Adds media action buttons. [See `actions`](/docs/configuration/actions/).
*: Required
**: Required with multiple sources
Field-level media options can override the media source defaults.
## Commit templates
`media[].commit.templates` overrides the global commit templates for that media source.
```yaml
media:
- name: images
input: media/images
output: /media/images
commit:
templates:
create: "chore(media): add {filename}"
update: "chore(media): update {filename}"
delete: "chore(media): remove {filename}"
rename: "chore(media): rename {oldFilename} -> {newFilename}"
```
min | Minimum number of items.
max | Maximum number of items.
collapsible | Make object or block items collapsible. Values: `true`, `false`, or an object.
### `collapsible`
Use `collapsible` on `object` or `block` lists to collapse each item in the editor.
You can use a boolean:
```yaml
- name: sections
type: object
list:
collapsible: true
fields:
- name: title
type: string
```
Or the object form:
Key | Description
--- | ---
collapsed | Values: `true`, `false`. Whether items start collapsed by default.
summary | Summary text shown for each collapsed item.
`summary` supports:
Token | Description
--- | ---
`{index}` | 1-based list item index.
`{fields.create | Allow creating new entries/files for this content entry.
rename | Allow renaming entries/files for this content entry.
delete | Allow deleting entries/files for this content entry.
## Defaults
Defaults depend on `type`:
Type | create | rename | delete
--- | --- | --- | ---
`collection` | `true` | `true` | `true`
`file` | `true` | `false` | `true`
## Notes
- `settings` (`.pages.yml`) are separate from `content`; deleting settings is disabled by default.
- Any key can be set to `false` to block that operation for the entry.
## Examples
### Disable delete for a single file
```yaml
content:
- name: site
label: Site settings
type: file
path: data/site.yml
operations:
delete: false
```
### Disable rename and delete for a collection
```yaml
content:
- name: posts
type: collection
path: content/posts
operations:
rename: false
delete: false
```
---
# Fields
Source: https://pagescms.org/docs/configuration/content/fields/
Define the editor schema for collections and files.
`fields` defines the editor schema.
## Keys
These keys apply to all field types unless noted otherwise.
Key | Description
--- | ---
name * | Field key used in stored data.
label | UI label for the field. Set `false` to hide it.
type * | Field type. Use `type` or `component`.
component | Reuse a field definition from `components`. Use `component` or `type`.
required | Marks the field as required.
pattern | Regex validation for supported field types.
hidden | Hides the field from the editor.
readonly | Shows the field value but prevents editing it. Inherited by nested object, block, and list fields.
description | Helper text shown below the field.
options | Field-specific options. See each field page for details.
*: Required
## `pattern`
`pattern` validates a field value against a regex.
Currently, it is supported by:
- `string`
- `text`
You can use either a string:
```yaml
pattern: "^[a-z0-9-]+$"
```
Or an object with a custom message:
```yaml
pattern:
regex: "^[A-Z]{3}-\\d{4}$"
message: "Use format ABC-1234"
```
## `component`
Use `component` to reference a reusable field definition from [`components`](/docs/configuration/components/).
A field must use exactly one of:
- `type`
- `component`
Example:
```yaml
components:
seo:
type: object
label: SEO
fields:
- name: title
type: string
- name: description
type: text
content:
- name: pages
type: collection
path: content/pages
fields:
- name: seo
component: seo
label: Meta
```
## `body` is a special key
For frontmatter formats, `body` maps to the file content below the frontmatter.
All other fields stay in frontmatter.
```yaml
fields:
- name: title
type: string
- name: body
type: rich-text
```
## Field types
Type | Description
--- | ---
[`block`](/docs/configuration/fields/block/) | Multiple object shapes in one list.
[`boolean`](/docs/configuration/fields/boolean/) | True/false toggle.
[`code`](/docs/configuration/fields/code/) | Code editor with syntax highlighting.
[`date`](/docs/configuration/fields/date/) | Date or date-time input.
[`file`](/docs/configuration/fields/file/) | File picker or uploader.
[`image`](/docs/configuration/fields/image/) | Image picker or uploader.
[`number`](/docs/configuration/fields/number/) | Numeric input.
[`object`](/docs/configuration/fields/object/) | Nested group of fields.
[`reference`](/docs/configuration/fields/reference/) | Link to another collection.
[`rich-text`](/docs/configuration/fields/rich-text/) | Rich text editor.
[`select`](/docs/configuration/fields/select/) | Fixed local options.
[`string`](/docs/configuration/fields/string/) | Single-line text input.
[`text`](/docs/configuration/fields/text/) | Multi-line plain text input.
[`uuid`](/docs/configuration/fields/uuid/) | UUID v4 field.
## Examples
### Frontmatter with body content
```yaml
fields:
- name: title
type: string
- name: published
type: boolean
- name: body
type: rich-text
```
### Nested object field
```yaml
fields:
- name: author
type: object
fields:
- name: name
type: string
- name: email
type: string
```
### String field with pattern
```yaml
fields:
- name: slug
type: string
required: true
pattern: "^[a-z0-9-]+$"
options:
minlength: 3
maxlength: 80
```
### Text field with custom pattern message
```yaml
fields:
- name: summary
type: text
required: true
pattern:
regex: "^(?s).{20,500}$"
message: "Summary must be between 20 and 500 characters"
options:
minlength: 20
maxlength: 500
```
---
# View
Source: https://pagescms.org/docs/configuration/content/view/
Control how collections are listed in the editor.
`view` only applies to collections.
It controls how the collection list is displayed.
## Keys
Key | Description
--- | ---
fields | Fields shown in the list, in order (e.g. `["title", "published", "author.name"]`).
primary | Field used as the main label. Defaults to `title` if present.
sort | Fields available for sorting.
search | Fields indexed for search.
default.search | Default search query.
default.sort | Default sort field.
default.order | Values: `asc`, `desc`.
layout | Values: `list`, `tree`.
node | Tree node config, as a string or object.
node.filename | Node filename in tree mode.
node.hideDirs | Values: `all`, `nodes`, `others`.
## Examples
### Basic list view
```yaml
view:
fields: [title, published, date]
primary: title
sort: [date, title]
default:
sort: date
order: desc
```
### Tree view
```yaml
view:
layout: tree
node:
filename: index.md
hideDirs: others
fields: [title]
primary: title
```
---
# Components
Source: https://pagescms.org/docs/configuration/components/
Reuse field definitions across multiple content models.
## What `components` does
Use `components` when the same field group appears in multiple places.
Typical examples:
- SEO fields,
- author objects,
- call-to-action blocks,
- repeated metadata groups.
Define the field once, then reference it from `content`.
## Example
```yaml
components:
seo:
type: object
label: SEO
fields:
- name: title
type: string
- name: description
type: text
content:
- name: pages
type: collection
path: content/pages
fields:
- name: heading
type: string
- name: seo
component: seo
label: Meta
```
## Override behavior
When you reference a component, field-level values can override component values.
In the example above:
- the component label is `SEO`,
- the field label overrides it to `Meta`.
The resolved field behaves like this:
```yaml
- name: seo
type: object
label: Meta
fields:
- name: title
type: string
- name: description
type: text
```
---
# Actions
Source: https://pagescms.org/docs/configuration/actions/
Add custom GitHub Actions buttons to Pages CMS.
## What actions are
Actions allow you to add custom buttons that trigger GitHub Actions. They can appear:
- at the repository level in the sidebar,
- in the header of collection pages, collection entry pages, file pages and media pages.
These actions start a GitHub Actions workflow with `workflow_dispatch` and [a `payload` input](#configuration-on-github) that contains contextual information about the trigger (e.g. sha, path of the entry, custom user inputs, etc).
## Keys
Each action may use the following keys:
Key | Description
--- | ---
name * | Internal action name.
label * | Button label shown in the UI.
workflow * | Workflow file name in `.github/workflows/`.
ref | Git ref used to dispatch the workflow. Use `current` to use the branch currently open in Pages CMS.
scope | Collection-only. Values: `collection`, `entry`.
cancelable | Whether the run can be cancelled from Pages CMS. Defaults to `true`.
confirm | Confirmation dialog config. Use `false` to skip confirmation.
fields | Extra input fields collected before dispatch.
*: Required
## Confirmation
Actions show a confirmation dialog by default when triggered.
Set `confirm: false` to skip it or customize the title, message and button label of the dialog:
```yaml
actions:
- name: deploy-site
label: Deploy site
workflow: pages-cms-action.yml
confirm:
title: Deploy site?
message: This will trigger the deployment workflow.
button: Deploy
```
## Extra fields
Use `fields` to collect extra values that will be passed to the GitHub Actions workflow via `payload.inputs`. Each field may use the following keys:
- `name`: field name (used in `payload.inputs`).
- `label`: field label.
- `type`: one of `text`, `textarea`, `select`, `checkbox`, or `number`.
- `required`: whether the field is required.
- `default`: default value for the field.
- `options`: an array of label/value objects for `select` fields.
The form for these values is shown in the same dialog before the action is dispatched. If `fields` are defined, the dialog is still shown even when `confirm: false`.
Example:
```yaml
actions:
- name: deploy-site
label: Deploy site
workflow: pages-cms-action.yml
fields:
- name: environment
label: Environment
type: select
required: true
default: staging
options:
- label: Staging
value: staging
- label: Production
value: production
- name: force
label: Force deploy
type: checkbox
default: false
```
## Configuration on GitHub
GitHub Actions need to be enabled for the repository. You will need to create a workflow that accepts a `payload` input:
```yaml
on:
workflow_dispatch:
inputs:
payload:
description: Pages CMS payload as JSON
required: true
type: string
```
Pages CMS will send one JSON object inside `inputs.payload` which includes:
- action metadata,
- repository metadata,
- triggering user,
- context,
- extra field values in `payload.inputs`.
Example shape:
```json
{
"source": "pages-cms",
"action": {
"name": "deploy-site",
"label": "Deploy site"
},
"repository": {
"owner": "pagescms",
"repo": "website",
"ref": "main",
"workflowRef": "main",
"sha": "abc123..."
},
"triggeredAt": "2026-03-30T12:00:00.000Z",
"triggerType": "rerun",
"rerunOfActionRunId": 42,
"triggeredBy": {
"userId": "...",
"name": "Ronan Berder",
"email": "...",
"githubUsername": "hunvreus",
"image": "..."
},
"context": {
"type": "entry",
"name": "posts",
"path": "content/posts/hello.md",
"data": {}
},
"inputs": {
"environment": "staging",
"force": false
}
}
```
Use `jq`, `node`, or `actions/github-script` inside the workflow to parse `payload.inputs`.
## Permissions and cancellation
- `Run again` is only available to GitHub users.
- `Cancel run` is available to GitHub users for any active run.
- Collaborators can only cancel their own active runs.
- `Cancel run` is only available once the GitHub workflow run exists.
- Set `cancelable: false` to disable cancellation for an action.
## Examples
### Root action
```yaml
actions:
- name: deploy-site
label: Deploy site
workflow: pages-cms-action.yml
ref: current
cancelable: false
```
### Collection action
```yaml
content:
- name: posts
label: Posts
type: collection
path: content/posts
fields:
- name: title
type: string
actions:
- name: rebuild-posts
label: Rebuild posts
scope: collection
workflow: pages-cms-collection-action.yml
- name: preview-post
label: Preview
scope: entry
workflow: pages-cms-entry-action.yml
```
### File and media
```yaml
content:
- name: site
label: Site settings
type: file
path: data/site.yml
fields:
- name: title
type: string
actions:
- name: validate-config
label: Validate config
workflow: pages-cms-file-action.yml
media:
- name: images
label: Images
input: media/images
output: /media/images
actions:
- name: optimize-images
label: Optimize images
workflow: pages-cms-media-action.yml
```
---
# Collaborators
Source: https://pagescms.org/docs/configuration/collaborators/
What collaborators can do and how their changes are committed.
## What collaborators are
Collaborators are invited by email.
Use them when someone needs to edit content or media but does not have a GitHub account.
## What collaborators can do
Collaborators can:
- open repositories they were invited to,
- edit content,
- edit media.
Content operations (`create`, `rename`, `delete`) still follow the repository configuration for each `content` entry. See [`content.operations`](/docs/configuration/content/operations/).
## What collaborators cannot do
Collaborators cannot:
- manage `.pages.yml`,
- manage collaborators,
- access cache admin features.
Those actions stay limited to GitHub users with repository access.
## How collaborator commits work
Collaborator writes use the GitHub App installation token for the target repository.
By default, Pages CMS does not send the collaborator's name and email as committer metadata.
That means:
- the write is authorized by the GitHub App,
- the commit uses the authenticated app/installation identity unless commit identity is explicitly set to `user`.
If you want collaborator writes to include the collaborator's name and email as committer metadata, set `settings.commit.identity: user` or override a specific schema with `commit.identity: user`.
## Migration
Collaborators live in the database, not in `.pages.yml`.
If you move to a new Pages CMS install, export and import collaborators separately.
---
# Settings
Source: https://pagescms.org/docs/configuration/settings/
Configure repository-wide behavior in Pages CMS.
## What `settings` does
Use `settings` for behavior that applies across the whole repository.
Typical uses:
- hide admin pages,
- preserve unmanaged keys when saving structured content,
- define default commit messages,
- choose commit identity behavior.
## Keys
Key | Description
--- | ---
hide | If `true`, hides the Settings page in the UI.
content | Controls how structured content is saved. [See `Content`](#content).
commit | Controls commit settings. [See `Commit`](#commit).
## Content
For now, `settings.content` only supports `merge`.
Value | Behavior
--- | ---
`false` | Default. Rewrite the file from the configured schema only. Anything outside the submitted editor output is removed.
`true` | Merge the submitted fields into the existing file before saving. Keys outside the schema are preserved unless the editor overwrites them.
## Commit
### Commit templates
`settings.commit.templates` defines the default commit message format for content and media changes.
Key | Default value
--- | ---
`create` | `Create {path} (via Pages CMS)`
`update` | `Update {path} (via Pages CMS)`
`delete` | `Delete {path} (via Pages CMS)`
`rename` | `Rename {oldPath} to {newPath} (via Pages CMS)`
`content[].commit.templates` and `media[].commit.templates` override these global templates.
Within these templates, you can use any of the following tokens:
Token | Description
--- | ---
{action} | Current action: `create`, `update`, `delete`, or `rename`.
{path} | File path.
{filename} | File name only.
{name} | Content or media entry name.
{owner} | Repository owner.
{repo} | Repository name.
{branch} | Current branch.
{user} | Current user identifier. Kept for compatibility.
{userName} | Current user display name when available.
{userEmail} | Current user email when available.
{oldPath} | Previous path. Rename only.
{newPath} | New path. Rename only.
{oldFilename} | Previous file name. Rename only.
{newFilename} | New file name. Rename only.
Prefer `{userName}` and `{userEmail}` in new templates. `{user}` remains available as a legacy fallback.
### Commit identity
`settings.commit.identity` controls whether Pages CMS sends explicit committer metadata on writes.
Value | Behavior
--- | ---
`app` | Default. Do not send explicit committer metadata. GitHub uses the authenticated writer identity for the request.
`user` | Send the current user's name and email as committer metadata when available.
`content[].commit.identity` and `media[].commit.identity` override the global setting for a specific schema.
## Examples
### Global commit templates and identity
```yaml
settings:
commit:
identity: app
templates:
create: "content(create): {path}"
update: "content(update): {path}"
delete: "content(delete): {path}"
rename: "content(rename): {oldPath} -> {newPath}"
```
### Global default with a media override
```yaml
settings:
commit:
identity: app
media:
- name: assets
input: public/uploads
output: /uploads
commit:
identity: user
templates:
update: "media(update): {path} by {userEmail}"
```
---
# Block field
Source: https://pagescms.org/docs/configuration/fields/block/
Let editors choose between multiple object shapes.
Use `type: block` for page-builder style sections where each item can be a different schema.
Each block item is stored as an object containing the selected block type and that block's fields.
## Options
Key | Description
--- | ---
blocks | List of available block definitions (e.g. `[{ name: "hero", component: "hero" }]`).
blockKey | Key used to store the selected block type. Defaults to `_block` (e.g. `blockKey: type` saves `type: hero`).
## Examples
### Simple block list
```yaml
- name: sections
label: Sections
type: block
list: true
blockKey: type
blocks:
- name: hero
component: hero
- name: text
fields:
- name: body
type: rich-text
```
Saved output:
```yaml
sections:
- type: hero
heading: Welcome
image: /images/hero.jpg
- type: text
body: Hello world
```
### Block with a nested object
```yaml
- name: sections
type: block
list: true
blockKey: type
blocks:
- name: cta
fields:
- name: button
type: object
fields:
- name: label
type: string
- name: url
type: string
```
Saved output:
```yaml
sections:
- type: cta
button:
label: Read more
url: /about
```
### Block with a nested list
```yaml
- name: sections
type: block
list: true
blockKey: type
blocks:
- name: faqs
fields:
- name: items
type: object
list: true
fields:
- name: heading
type: string
- name: text
type: rich-text
```
Saved output:
```yaml
sections:
- type: faqs
items:
- heading: What is Pages CMS?
text: A Git-backed CMS.
- heading: Is it open source?
text: Yes.
```
### Nested repeated data inside a block
```yaml
- name: faqs
fields:
- name: items
type: object
list: true
fields:
- name: heading
type: string
- name: text
type: rich-text
```
Saved output:
```yaml
sections:
- type: faqs
items:
- heading: What is Pages CMS?
text: A Git-backed CMS.
- heading: Is it open source?
text: Yes.
```
This is not supported as a block root shape:
```yaml
- name: faqs
list: true
component: faqs
```
If you need repeated structured data inside a block, nest a list field inside the block object.
---
# Boolean field
Source: https://pagescms.org/docs/configuration/fields/boolean/
True/false toggle.
Use for flags like `published`, `featured`, or `archived`.
## Options
This field has no field-specific options.
## Examples
### Basic boolean
```yaml
- name: published
type: boolean
default: true
```
---
# Code field
Source: https://pagescms.org/docs/configuration/fields/code/
Code editor with syntax highlighting.
Use for snippets, templates, or small config blocks.
## Options
Key | Description
--- | ---
format | Values: `yaml`, `yml`, `javascript`, `js`, `jsx`, `typescript`, `ts`, `tsx`, `json`, `html`, `htm`, `markdown`, `mdx`.
## Examples
### JavaScript snippet
```yaml
- name: snippet
type: code
options:
format: javascript
```
### MDX snippet
```yaml
- name: article
type: code
options:
format: mdx
```
---
# Date field
Source: https://pagescms.org/docs/configuration/fields/date/
Date or date-time input with formatting and bounds.
Use for publish dates, events, or scheduling metadata.
## Options
Key | Description
--- | ---
time | If `true`, includes a time picker.
format | Output format string, using [`date-fns`](https://date-fns.org/) tokens (e.g. `"yyyy-MM-dd"`).
min | Lower allowed value (e.g. `"2025-01-01"`).
max | Upper allowed value (e.g. `"2025-12-31"`).
step | Input step value (e.g. `60`).
## Notes
- Date fields initialize to the current local date.
- Datetime fields (`time: true`) initialize to the current local date and time.
- To keep a date field empty by default, set `default: ""`.
## Examples
### Date and time
```yaml
- name: publish_at
type: date
default: ""
options:
time: true
format: yyyy-MM-dd'T'HH:mm
```
---
# File field
Source: https://pagescms.org/docs/configuration/fields/file/
Select or upload non-image files from media.
Use for PDFs, ZIPs, docs, audio, and similar files.
## Options
Key | Description
--- | ---
media | Named media config to use (e.g. `"docs"`).
path | Default browsing folder (e.g. `"contracts"`).
multiple | Allow multiple files (e.g. `{ max: 5 }`).
extensions | Allowed file extensions (e.g. `["pdf", "zip"]`).
categories | Allowed file categories. Values: `image`, `document`, `video`, `audio`, `compressed`.
unique | If `true`, disallows duplicate file paths when `multiple` is enabled.
rename | Controls upload renaming. Use `false` to keep the original filename, `true` or `safe` to slugify it, or `random` for a generated name.
## Examples
### Named media config and default folder
```yaml
- name: brochure
label: Brochure
type: file
options:
media: docs
path: public/files/brochures
```
### Multiple downloadable resources
```yaml
- name: resources
label: Resources
type: file
options:
categories: [document]
multiple:
max: 5
unique: true
rename: true
```
### Specific file extensions
```yaml
- name: archive
label: Archive
type: file
options:
extensions: [zip, tar, gz]
```
---
# Image field
Source: https://pagescms.org/docs/configuration/fields/image/
Select or upload images from media.
Use for cover images, thumbnails, galleries, logos.
## Options
Key | Description
--- | ---
media | Named media config to use (e.g. `"images"`).
path | Default browsing folder (e.g. `"blog"`).
multiple | Allow multiple images (e.g. `{ max: 6 }`).
extensions | Allowed image extensions (e.g. `["jpg", "png", "webp"]`).
categories | Allowed image categories. Values: `image`.
unique | If `true`, disallows duplicate image paths when `multiple` is enabled.
rename | Controls upload renaming. Use `false` to keep the original filename, `true` or `safe` to slugify it, or `random` for a generated name.
## Examples
### Named media config and default folder
```yaml
- name: cover
label: Cover image
type: image
options:
media: images
path: public/images/posts
```
### Multiple images
```yaml
- name: gallery
label: Gallery
type: image
options:
multiple:
max: 6
unique: true
extensions: [jpg, png, webp]
rename: true
```
### Category-based restriction
```yaml
- name: thumbnail
label: Thumbnail
type: image
options:
categories: [image]
```
---
# Number field
Source: https://pagescms.org/docs/configuration/fields/number/
Numeric input for integers and decimals.
Use for prices, weights, scores, or rankings.
## Options
Key | Description
--- | ---
min | Minimum value (e.g. `0`).
max | Maximum value (e.g. `100`).
## Examples
### Basic number
```yaml
- name: price
type: number
options:
min: 0
```
---
# Object field
Source: https://pagescms.org/docs/configuration/fields/object/
Group nested fields under one key.
Use for structured data like addresses, SEO, or author profiles.
## Options
Key | Description
--- | ---
None | This field has no field-specific options. Define nested `fields` instead.
## Behavior
- `required` applies to the object itself, not automatically to every child.
- For optional objects, child `required` rules apply only once the object has meaningful content.
- `readonly` is inherited by the whole nested subtree.
### Readonly object list
```yaml
- name: authors
type: object
readonly: true
list: true
fields:
- name: name
type: string
- name: email
type: string
```
In this example, `name` and `email` behave as readonly without needing their own `readonly: true`.
## List summary tokens
If the object field is also a list and uses `list.collapsible.summary`, the summary string supports tokens.
Without a custom summary, Pages CMS falls back to `Item #n`.
Token | Description
--- | ---
`{index}` | 1-based list item index.
`{fields.collection | Target collection name (e.g. `"authors"`).
multiple | If `true`, allows many references.
min | Minimum number of selected references when `multiple` is enabled.
max | Maximum number of selected references when `multiple` is enabled.
search | Comma-separated fields used for lookup (e.g. `"name,email"`).
value | Template for the stored value (e.g. `"{path}"`).
label | Template for the displayed label (e.g. `"{name}"`).
## Template tokens
`value` and `label` support template strings.
Token | Description
--- | ---
`{path}` | Entry path.
`{name}` | Entry name.
`{primary}` | Primary field value from the referenced collection. Uses `view.primary`, otherwise `title`, otherwise the first field.
`{fields.format | Values: `markdown`, `html`.
switcher | Show or hide the `Editor` / `Source` mode switch. Defaults to `true`.
media | Media source for inserted images (e.g. `"content_images"`). Set to `false` to disable media.
path | Default image folder (e.g. `"blog"`).
extensions | Allowed image extensions (e.g. `["png", "webp"]`).
categories | Allowed image categories. Values: `image`.
rename | Controls upload renaming. Use `false` to keep the original filename, `true` or `safe` to slugify it, or `random` for a generated name.
## Examples
### Markdown with media
```yaml
- name: body
label: Body
type: rich-text
options:
media: content_images
path: public/images/blog
rename: true
switcher: true
```
### HTML output
```yaml
- name: content
label: Content
type: rich-text
options:
format: html
```
### Hide the mode switch
```yaml
- name: excerpt
label: Excerpt
type: rich-text
options:
switcher: false
```
### Disable media
```yaml
- name: notes
label: Notes
type: rich-text
options:
media: false
```
---
# Select field
Source: https://pagescms.org/docs/configuration/fields/select/
Choose from a predefined list of local options.
Use for tags, categories, statuses, and other fixed option lists.
## Options
Key | Description
--- | ---
values | Required. Static options (e.g. `["Draft", "Review", "Published"]` or `[{ name: draft, label: Draft }]`).
multiple | If `true`, allows multiple values.
min | Minimum number of selected values when `multiple` is enabled.
max | Maximum number of selected values when `multiple` is enabled.
placeholder | Custom placeholder text (e.g. `"Select a status"`).
`select` only supports predefined local options. If you need to load entries dynamically or from another source, use [reference](/docs/configuration/fields/reference/) instead.
## Examples
### Single value
```yaml
- name: status
type: select
options:
values: [Draft, Review, Published]
```
### Multiple + named values
```yaml
- name: categories
type: select
options:
values:
- name: art
label: Art
- name: fashion
label: Fashion
- name: movies
label: Movies
- name: music
label: Music
```
---
# String field
Source: https://pagescms.org/docs/configuration/fields/string/
Single-line text input.
Use for short values like titles, slugs, and names.
## Options
Key | Description
--- | ---
minlength | Minimum allowed length (e.g. `3`).
maxlength | Maximum allowed length (e.g. `120`).
## Examples
### Basic string
```yaml
- name: title
type: string
options:
maxlength: 120
```
---
# Text field
Source: https://pagescms.org/docs/configuration/fields/text/
Multi-line plain text.
Use for summaries, excerpts, and notes.
## Options
Key | Description
--- | ---
minlength | Minimum allowed length (e.g. `20`).
maxlength | Maximum allowed length (e.g. `280`).
## Examples
### Basic text
```yaml
- name: excerpt
type: text
options:
maxlength: 280
```
---
# UUID field
Source: https://pagescms.org/docs/configuration/fields/uuid/
Generate and store a UUID v4.
Use for stable IDs independent from filenames or titles.
## Options
Key | Description
--- | ---
editable | If `true`, users can type a custom UUID value.
generate | If `false`, hides the "generate new UUID" button.
## Notes
- A UUID is auto-generated when the field has no explicit `default`.
- To force an empty initial value, set `default: ""`.
## Examples
### Generated UUID
```yaml
- name: id
type: uuid
options:
editable: false
```
---
# Deploy on Vercel
Source: https://pagescms.org/docs/guides/installing/vercel/
Deploy Pages CMS on Vercel with PostgreSQL and a GitHub App.
## Step 1: Create a PostgreSQL database
You will need a `DATABASE_URL`.
If you run migrations during deployment, prefer the Supabase Session Pooler for
DATABASE_URL. Direct connections can cause connectivity issues on some
hosting providers.
On Vercel with a pooled Supabase connection string, set
POSTGRES_MAX_CONNECTIONS=1. The limit is per Vercel instance, so higher
values can exhaust database connections quickly.
If you run migrations during deployment, prefer the Supabase Session Pooler for
DATABASE_URL. Direct connections can cause connectivity issues on some
hosting providers.
media | Defines where uploaded files are stored and what URLs are written. [See `media`](/docs/configuration/media/).
content | Defines editable collections and files. [See `content`](/docs/configuration/content/).
components | Reuses shared field definitions. [See `components`](/docs/configuration/components/).
settings | Sets repository-wide behavior such as merge mode and commit templates. [See `settings`](/docs/configuration/settings/).
actions | Adds repository-level GitHub Actions buttons. [See `actions`](/docs/configuration/actions/).
## Read order
Start with this order:
1. Define `media`.
2. Define `content`.
3. Add `components` if fields repeat.
4. Add `settings` if you need global behavior.
5. Add `actions` if you want custom workflow buttons.
## Minimal example
```yaml
media: media
content:
- name: posts
label: Posts
type: collection
path: content/posts
fields:
- name: title
type: string
- name: body
type: rich-text
```
## Example with a collection and a single file
```yaml
media:
input: src/media
output: /media
content:
- name: posts
label: Posts
type: collection
path: src/posts
fields:
- name: title
type: string
- name: body
type: rich-text
- name: site
label: Site settings
type: file
path: src/_data/site.json
fields:
- name: title
type: string
- name: description
type: text
- name: url
type: string
actions:
- name: deploy-site
label: Deploy site
workflow: pages-cms-action.yml
```
---
# Overview
Source: https://pagescms.org/docs/configuration/content/
Define editable collections and files in your repository.
## Overview
`content` defines what editors can edit.
Each entry is either:
- a `collection` for many files with the same schema
- a `file` for one file with its own schema
- a `group` for organizing files and collections in the sidebar
`group` is navigation-only. It can contain nested `group`, `collection`, and `file` entries, but it does not create its own editor route.
Collections and files can also define `actions`. See [`actions`](/docs/configuration/actions/).
## Keys
Each `content` entry may use the following keys:
Key | Description
--- | ---
name * | Unique internal name (e.g. `"posts"`).
label | UI label (e.g. `"Blog posts"`).
type * | Values: `collection`, `file`, `group`.
path * | Folder for collections or file path for single files (e.g. `"content/posts"`, `"data/site.yml"`). Not used by `group`.
fields | Field definitions shown in the editor (e.g. `[{ name: "title", type: "string" }]`). Fields support options like `required`, `hidden`, and `readonly`. Read more about: [`fields`](/docs/configuration/content/fields/), [editors](/docs/configuration/content/editors/)
filename | Collection filename template or object config (e.g. `"{primary}.md"` or `{ template: "{year}-{month}-{day}-{primary}.md", field: create }`). [Read more about `filename`](/docs/configuration/content/filename/)
exclude | Files to ignore in a collection (e.g. `["README.md"]`).
format | File format. Values include `yaml-frontmatter`, `json-frontmatter`, `toml-frontmatter`, `yaml`, `json`, `toml`, `datagrid`, `code`, `raw`. [Read more about editors](/docs/configuration/content/editors/)
delimiters | Custom frontmatter delimiters (e.g. `"+++"`, `[""]`).
subfolders | Values: `true`, `false`. Enables or disables nested folders in collections.
list | Repeat a field as an array, or for `type: file`, store the whole file as a top-level array. [Read more about `list`](/docs/configuration/content/list/)
view | Collection list settings for fields, sorting, search, and tree mode (e.g. `{ primary: "title", sort: "date", order: "desc" }`). [Read more about `view`](/docs/configuration/content/view/)
operations | Per-entry create/rename/delete controls (e.g. `{ delete: false }`). [Read more about `operations`](/docs/configuration/content/operations/).
commit | Per-entry commit settings (e.g. `{ identity: "user" }`). [See `settings`](/docs/configuration/settings/).
actions | Adds collection or file action buttons (e.g. `[{ name: "preview", label: "Preview", workflow: "pages-cms-file-action.yml" }]`). [See `actions`](/docs/configuration/actions/).
items | Child entries inside a `group` (e.g. nested `group`, `collection`, or `file` entries).
*: Required
## Examples
### Collection
```yaml
content:
- name: posts
label: Posts
type: collection
path: content/posts
fields:
- name: title
type: string
- name: body
type: rich-text
actions:
- name: rebuild-posts
label: Rebuild posts
scope: collection
workflow: pages-cms-collection-action.yml
- name: preview-post
label: Preview
scope: entry
workflow: pages-cms-entry-action.yml
```
### Single file
```yaml
content:
- name: site
label: Site settings
type: file
path: data/site.yml
fields:
- name: title
type: string
- name: description
type: text
actions:
- name: validate-config
label: Validate config
workflow: pages-cms-file-action.yml
```
### Nested groups
Use `type: group` to organize large content menus without changing how content is stored internally.
```yaml
content:
- name: docs
label: Docs
type: group
items:
- name: pages
label: Pages
type: collection
path: content/pages
fields:
- name: title
type: string
- name: references
label: References
type: group
items:
- name: publications
label: Publications
type: file
path: data/publications.json
format: json
list: true
fields:
- name: title
type: string
```
---
# Install locally
Source: https://pagescms.org/docs/guides/installing/
Run Pages CMS locally with PostgreSQL, environment variables, and a GitHub App.
## Step 1: Start PostgreSQL
```bash
docker run --name pagescms-db -e POSTGRES_USER=pagescms -e POSTGRES_PASSWORD=pagescms -e POSTGRES_DB=pagescms -p 5432:5432 -d postgres:16
```
## Step 2: Install dependencies
```bash
npm install
```
## Step 3: Create `.env.local`
```bash
DATABASE_URL=postgresql://pagescms:pagescms@localhost:5432/pagescms
BETTER_AUTH_SECRET=your-random-secret
CRYPTO_KEY=your-random-secret
```
You can generate secrets with:
```bash
openssl rand -base64 32
```
## Step 4: Create the GitHub App
Use [the helper](/docs/guides/installing/github-app/#using-the-helper):
```bash
npm run setup:github-app -- --base-url http://localhost:3000 --env .env.local
```
If you need other helper options, see [GitHub App helper](/docs/guides/installing/github-app/#using-the-helper).
## 5. Run migrations
```bash
npm run db:migrate
```
## 6. Start the app
```bash
npm run dev
```
If you need GitHub webhooks to hit your local app, use a public tunnel URL (e.g. [ngrok](https://ngrok.com/)) as the helper `--base-url`.
---