Git CMS
The Git CMS is the backend in @plutocms/core. It needs no database. Content is Markdown files with YAML frontmatter in a Git repository. Editors change the files in the Pluto admin.
Pluto works out of the box with local Git. In development, each save in the admin is a commit in the local repository of your project. You push when you are ready. Local Git publishing works in development only: to publish from a deployed site, connect GitHub. A deployed site reads the files live through the GitHub API, with a cache.
How it works
- Each save is a commit: in the local repository in development, in the GitHub repository on a deployed site. On GitHub, the commit carries the name of the editor.
- On a deployed site, users with push access to the repository are editors. They sign in with a GitHub App. In development, you are signed in as a local user.
- The public site reads published posts at run time. A save publishes in seconds, with no new build.
- A draft is a post with
status: draftin its frontmatter. - A save that changes nothing makes no commit.
On unless a backend layer is present
plutoGithub.enabled is 'auto' by default. It also accepts true and false.
| Value | Result |
|---|---|
'auto' | The CMS is on, unless the app has a backend layer. With a backend layer, the CMS is off and the module logs nothing. |
true | The CMS is on. With a backend layer, the module logs the error below and stays off. |
false | The CMS is off. |
A backend layer is one of these:
- A layer with
$meta.name === 'supabase'. - The
@nuxtjs/supabasemodule. - A layer with
$meta.plutoBackend: true. See Create a layer.
When the CMS is off, the module adds nothing: no runtime config key, handler, plugin, page, middleware, or import.
The standalone CMS cannot run with the supabase layer. Remove plutoGithub.enabled or the supabase layer.
Start fast
The quickest start is the template. You need no GitHub App on your machine. After a deploy, the admin creates the App for you: see the Quickstart.
npm create pluto@latest my-blog -t blog --backend git
Or use the CMS in an existing app. Install @plutocms/core first. extends does not install it.
npm install @plutocms/corepnpm add @plutocms/coreyarn add @plutocms/corebun add @plutocms/coredeno add npm:@plutocms/coreThen extend only @plutocms/core. The app needs no other layer, and it sets no plutoGithub option:
export default defineNuxtConfig({
extends: ['@plutocms/core']
})
In development, the CMS reads and writes the local content/ folder, and commits each save to the local Git repository. In production, it uses the GitHub repository that Pluto finds. See Reference. Posts live in content/posts/<slug>.md.
File layout
A file is <dir>/<type.source>/<slug>.md. The built-in post type uses source: 'posts'. With the default dir, a post is content/posts/<slug>.md.
- The item
idis the slug. It is the file name. It is not a frontmatter key. - A slug uses lowercase letters, numbers, and single dashes.
- The first
richtextfield is the Markdown body. - Every other field, except the slug field, is a frontmatter key.
- An update keeps the frontmatter keys that the type does not know.
- A file with no status key counts as the default status. For posts, that is
draft. - An update whose new file text equals the stored text makes no commit. The
updatedtimestamp alone is not a change. The call returns the stored item with the same_version.
Nested content types
A content type can store its items in nested folders. Set files: { nested: true } on the type. A docs site needs this. The docs layer uses it.
files: {
nested: true, // ids are paths: '1.start/2.install'
exclude: ['index', 'drafts/'], // an id, or a folder with a trailing slash
frontmatter: { read, write } // pure functions: (frontmatter) => frontmatter
}
- The
idis the path under the type folder, without.md. Each segment is a slug with an optional numeric prefix, such as2.install. An id has at most 6 segments and 255 characters. source: ''means the contentdiritself. Only a nested type can use it.- The slug field holds the whole id. A new path in the slug field creates the item in a folder. An update with a new path moves the file in one commit. A move onto a taken path gives 409.
- The default sort of a nested type is
compareContentIds. In each segment,indexcomes first. Then the numeric prefix sorts as a number. Then the name decides. SetdefaultSort: { field: 'id', direction: 'asc' }for a nested type that also has timestamps. frontmatter.readruns on a copy after Pluto parses a file. Use it to add flat keys that the fields read.frontmatter.writeruns before Pluto writes a file. Use it to fold the flat keys back. Keys that the type does not know must stay.- Every store has
listTree. It reads every.mdfile under a folder. The GitHub store does this with one GraphQL call. All stores skip dot folders and dot files. - The admin pages of a nested item use a catch-all route:
/admin/content/<type>/<id path>.contentIdPath(id)encodes an id for a URL and keeps the/. - The public read routes also read a nested item: the id can hold slashes. See Public read.
What is in this section
Planned
The source lists these for a later version: revision history, settings stored in the repository, a password provider, scheduled posts, and configurable collections.