Bundling
This tutorial guides you through the steps for adding Static CMS via a package manager to a site that is built with a common static site generator. If you want to start form a template, the Next Template provides a great example of bundling in action.
Installation
To get started you need to install Static CMS via a package manager and save it to your project:
// npm
npm install @staticcms/core@latest
// yarn
yarn add @staticcms/core@latest
Then create a new route for your project (for instance at /admin
), then import Static CMS and its styles:
import CMS from '@staticcms/core';
import '@staticcms/core/dist/main.css';
The default export is a CMS object, which has an init
method that takes an object with a config
attribute. The config
attribute is an object representing the configuration options. You can use destructuring assignment syntax as shorthand:
import CMS from '@staticcms/core';
import '@staticcms/core/dist/main.css';
import config from './config';
// Initialize the CMS object
CMS.init({ config });
// Now the registry is available via the CMS object.
CMS.registerPreviewTemplate('my-template', MyTemplate);
Note: Wherever you initialize Static CMS (via CMS.init()
), it takes over the current page. Make sure you only run the initialization code on your CMS page.
If the CMS object is initialized without being passed an object with a valid config attribute, it will try to fetch and read a config.yml
file, via http, within the same path where Static CMS resides (/admin/config.yml
).
import CMS from '@staticcms/core';
import '@staticcms/core/dist/main.css';
// Initialize Static CMS object
CMS.init();
// Now the registry is available via the CMS object.
CMS.registerPreviewTemplate('my-template', MyTemplate);
Note: Because config.yml
is requested via http, make sure <siteurl>/admin/config.yml
exists as an endpoint on your build. If the file is not placed in the public folder, this might not be the default behaviour for your static site generator.
Make sure the file containing the CMS object will be built as a page, with @staticcms/core
bundled, and that the code including CMS.init()
will run on the client. This is what might take some time, as it will be done differently based on your static site generator. Check your static site generators's documentation for further details.
Configuration
Configuration is different for every site, so we will break it down into parts. Add all the code snippets in this section to your admin/config.js
file (which is passed into the CMS.init({ config })
call).
Backend
We are using Netlify for our hosting and authentication in this tutorial, so backend configuration is fairly straightforward.
For GitHub and GitLab repositories, you can start your Static CMS config
file with these lines:
backend: {
name: 'git-gateway',
branch: 'main' // Branch to update (optional; defaults to main)
},
(For Bitbucket repositories, use the Bitbucket backend instructions instead.)
The configuration above specifies your backend protocol and your publication branch. Git Gateway is an open source API that acts as a proxy between authenticated users of your site and your site repo. (We will get to the details of that in the Authentication section below.) If you leave out the branch
declaration, it defaults to main
.
Editorial Workflow
By default, saving a post in the CMS interface pushes a commit directly to the publication branch specified in backend
. However, you also have the option to enable the Editorial Workflow, which adds an interface for drafting, reviewing, and approving posts. To do this, add the following line to your config.yml
:
publish_mode: editorial_workflow
Media and Public Folders
Static CMS allows users to upload images directly within the editor. For this to work, Static CMS needs to know where to save them. If you already have an images
folder in your project, you could use its path, possibly creating an uploads
sub-folder, for example:
media_folder: 'images/uploads', // Media files will be stored in the repo under images/uploads
If you are creating a new folder for uploaded media, you will need to know where your static site generator expects static files. You can refer to the paths outlined above in App File Structure, and put your media folder in the same location where you put the admin
folder.
Note that themedia_folder
file path is relative to the project root, so the example above would work for Jekyll, GitBook, or any other generator that stores static files at the project root. However, it would not work for Hugo, Hexo, Middleman or others that store static files in a subfolder. Here is an example that could work for a Hugo site:
media_folder: 'static/images/uploads', // Media files will be stored in the repo under static/images/uploads
public_folder: '/images/uploads', // The src attribute for uploaded media will begin with /images/uploads
The configuration above adds a new setting, public_folder
. While media_folder
specifies where uploaded files are saved in the repo, public_folder
indicates where they are found in the published site. Image src
attributes use this path, which is relative to the file where it is called. For this reason, we usually start the path at the site root, using the opening /
.
If public_folder
is not set, Static CMS defaults to the same value as media_folder
, adding an opening /
if one is not included.
Collections
Collections define the structure for the different content types on your static site. Since every site is different, the collections
settings differ greatly from one site to the next.
Let us say your site has a blog, with the posts stored in _posts/blog
, and files saved in a date-title format, like 1999-12-31-lets-party.md
. Each post begins with settings in yaml-formatted front matter, like so:
---
layout: blog
title: "Let's Party"
date: 1999-12-31 11:59:59 -0800
thumbnail: '/images/prince.jpg'
rating: 5
---
This is the post body, where I write about our last chance to party before the Y2K bug destroys us all.
Given this example, our collections
settings would look like this in your Static CMS config
file:
collections: [
{
name: 'blog', // Used in routes, e.g., /admin/collections/blog
label: 'Blog', // Used in the UI
folder: '_posts/blog', // The path to the folder where the documents are stored
create: true, // Allow users to create new documents in this collection
slug: '{{year}}-{{month}}-{{day}}-{{slug}}', // Filename template, e.g., yyyy-MM-dd-title.md
fields: [ // The fields for each document, usually in front matter
{ label: 'Layout', name: 'layout', widget: 'hidden', default: 'blog' },
{ label: 'Title', name: 'title', widget: 'string' },
{ label: 'Publish Date', name: 'date', widget: 'datetime' },
{ label: 'Featured Image', name: 'thumbnail', widget: 'image' },
{ label: 'Rating (scale of 1-5)', name: 'rating', widget: 'number' },
{ label: 'Body', name: 'body', widget: 'markdown' },
],
},
],
Let us break that down:
Field | Description |
---|---|
name | Post type identifier, used in routes. Must be unique. |
label | What the admin UI calls the post type. |
folder | Where files of this type are stored, relative to the repo root. |
create | Set to true to allow users to create new files in this collection. |
slug | Template for filenames. {{ year }} , {{ month }} , and {{ day }} pulls from the post's date field or save date. {{ slug }} is a url-safe version of the post's title . Default is simply {{ slug }} . |
fields | Fields listed here are shown as fields in the content editor, then saved as front matter at the beginning of the document (except for body , which follows the front matter). |
As described above, the widget
property specifies a built-in or custom UI widget for a given field. When a content editor enters a value into a widget, that value is saved in the document front matter as the value for the name
specified for that field. A full listing of available widgets can be found in the Widgets doc.
Based on this example, you can go through the post types in your site and add the appropriate settings to your Static CMS config
file. Each post type should be listed as a separate node under the collections
field. See the Collections reference doc for more configuration options.
Filter
The entries for any collection can be filtered based on the value of a single field. The example collection below only shows post entries with the value en
in the language
field.
collections: [
{
name: 'posts',
label: 'Post',
folder: '_posts',
filter: {
field: 'language',
value: 'en',
},
fields: [
{
name: 'language',
label: 'Language',
},
],
},
],
Authentication
Now that you have your Static CMS files in place and configured, all that is left is to enable authentication. We are using the Netlify platform here because it is one of the quickest ways to get started, but you can learn about other authentication options in the Backends doc.
Setup on Netlify
Netlify offers a built-in authentication service called Identity. In order to use it, connect your site repo with Netlify.
Enable Identity and Git Gateway
Netlify's Identity and Git Gateway services allow you to manage CMS admin users for your site without requiring them to have an account with your Git host or commit access on your repo. From your site dashboard on Netlify:
- Go to Settings > Identity, and select Enable Identity service.
- Under Registration preferences, select Open or Invite only. In most cases, you want only invited users to access your CMS, but if you are just experimenting, you can leave it open for convenience.
- If you'd like to allow one-click login with services like Google and GitHub, check the boxes next to the services you'd like to use, under External providers.
- Scroll down to Services > Git Gateway, and click Enable Git Gateway. This authenticates with your Git host and generates an API access token. In this case, we are leaving the Roles field blank, which means any logged in user may access Static CMS. For information on changing this, check the Netlify Identity documentation.
Add the Netlify Identity Widget
With the backend set to handle authentication, now you need a frontend interface to connect to it. The open source Netlify Identity Widget is a drop-in widget made for just this purpose. To include the widget in your site, add the following script tag in two places:
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
Add this to the <head>
of your CMS index page at /admin/index.html
, as well as the <head>
of your site's main index page. Depending on how your site generator is set up, this may mean you need to add it to the default template, or to a "partial" or "include" template. If you can find where the site stylesheet is linked, that is probably the right place. Alternatively, you can include the script in your site using Netlify's Script Injection feature.
When a user logs in with the Netlify Identity widget, an access token directs to the site homepage. In order to complete the login and get back to Static CMS, redirect the user back to the /admin/
path. To do this, add the following script before the closing body
tag of your site's main index page:
<script>
if (window.netlifyIdentity) {
window.netlifyIdentity.on('init', user => {
if (!user) {
window.netlifyIdentity.on('login', () => {
document.location.href = '/admin/';
});
}
});
}
</script>
Note: This example script requires modern JavaScript and does not work on IE11. For legacy browser support, use function expressions (function () {}
) in place of the arrow functions (() => {}
), or use a transpiler such as Babel.
Accessing Static CMS
Your site CMS is now fully configured and ready for login!
If you set your registration preference to "Invite only," invite yourself (and anyone else you choose) as a site user. To do this, select the Identity tab from your site dashboard, and then select the Invite users button. Invited users receive an email invitation with a confirmation link. Clicking the link will take you to your site with a login prompt.
If you left your site registration open, or for return visits after confirming an email invitation, access your site's CMS at yoursite.com/admin/
.
Note: No matter where you access Static CMS — whether running locally, in a staging environment, or in your published site — it always fetches and commits files in your hosted repository (for example, on GitHub), on the branch you configured in your Static CMS config
file. This means that content fetched in the admin UI matches the content in the repository, which may be different from your locally running site. It also means that content saved using the admin UI saves directly to the hosted repository, even if you are running the UI locally or in staging.
Happy posting!