Quick Start

There are many ways to add Netlify CMS to your static site. This guide will take you through one of the quickest methods, which takes advantage of Netlify’s hosting and authentication provider services.
Storage and Authentication

Netlify CMS relies on the GitHub API for managing files, so you’ll need to have your site stored in a GitHub repo. (If you’re partial to another Git hosting service, you can file a feature request, or help us add it.) To connect to the repo and make changes, the app needs to authenticate with the GitHub API. You can roll your own service for doing this, but we’re going to use Netlify in this example.
Hosting with Netlify

In order to use Netlify’s authentication provider service, you’ll need to connect your site repo with Netlify. Netlify has published a general Step-by-Step Guide for this, along with detailed guides for many popular static site generators, including Jekyll, Hugo, Hexo, Middleman, Gatsby and more.
Authenticating with GitHub

In order to connect Netlify CMS with your GitHub repo, you’ll first need to register it as an authorized application with your GitHub account:

Go to your account Settings page on GitHub, and click Oauth Applications under Developer Settings (or use this shortcut).
Click Register a new application.
For the Authorization callback URL, enter https://api.netlify.com/auth/done. The other fields can contain anything you want.

GitHub Oauth Application setup example

When you complete the registration, you’ll be given a Client ID and a Client Secret for the app. You’ll need to add these to your Netlify project:

Go to your Netlify dashboard and click on your project.
Click the Access tab.
Under Authentication Providers, click Install Provider.
Select GitHub and enter the Client ID and Client Secret, then save.

App File Structure

All Netlify CMS files are contained in a static admin folder, stored at the root of the generated site. Where you store this in the source files depends on your static site generator. Here’s the the static file location for a few of the most popular static site generators:
These generators …     store static files in
Jekyll, GitBook     / (project root)
Hugo, Gatsby*     /static
Hexo, Middleman     /source
Spike     /views

Notes: – Gatsby treats the static folder more strictly and will not render the admin page as the other generators. You will have to make a page component containing the necessary scripts of the Netlify CMS app in the admin page. However, the config.yml is to be placed in the static folder in the same way.

If your generator isn’t listed here, you can check its documentation, or as a shortcut, look in your project for a CSS or images folder. They’re usually processed as static files, so it’s likely you can store your admin folder next to those. (When you’ve found the location, feel free to add it to these docs by filing a pull request!).

Inside the admin folder, you’ll create two files:

admin
├ index.html
└ config.yml

The first file, admin/index.html, is the entry point for the Netlify CMS admin interface. This means that users can navigate to yoursite.com/admin to access it. On the code side, it’s a basic HTML starter page that loads the necessary CSS and JavaScript files. In this example, we pull those files from a public CDN:

<!doctype html>
<html>
<head>
<meta charset=”utf-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″ />
<title>Content Manager</title>

<link rel=”stylesheet” href=”https://unpkg.com/netlify-cms@~0.4/dist/cms.css” />

</head>
<body>
<script src=”https://unpkg.com/netlify-cms@~0.4/dist/cms.js”></script>
</body>
</html>

The second file, admin/config.yml, is the heart of your Netlify CMS installation, and a bit more complex. The next section covers the details.
Configuration

Configuration will be different for every site, so we’ll break it down into parts. All code snippets in this section will be added to your admin/config.yml file.
Backend

Because we’re using GitHub and Netlify for our hosting and authentication, backend configuration is fairly strightforward. You can start your config.yml file with these lines:

backend:
name: github
repo: owner-name/repo-name # Path to your Github repository
branch: master # Branch to update

This names GitHub as the authentication provider, points to the repo location on github.com, and declares the branch where you want to merge changes. If you leave out the branch declaration, it will default to master.
Editorial Workflow

By default, saving a post in the CMS interface will push a commit directly to the 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, simply add the following line to your config.yml:

publish_mode: editorial_workflow

Media and Public Folders

Netlify CMS allows users to upload images directly within the editor. For this to work, the 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’re creating a new folder for uploaded media, you’ll 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. It would not, however, work for Hugo, Hexo, Middleman or others that use a different path. Here’s 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

This configuration adds a new setting, public_folder. While media_folder specifies where uploaded files will be saved in the repo, public_folder indicates where they can be found in the generated site. This path is used in image src attributes and is relative to the file where it’s called. For this reason, we usually start the path at the site root, using the opening /.

If public_folder is not set, Netlify CMS will default 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 will be very different from one site to the next.

Let’s 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:

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’s break that down:
name     Post type identifier, used in routes. Must be unique.
label     What the post type will be called in the admin UI.
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}} will pull 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). Each field contains the following properties:

label: Field label in the editor UI.
name: Field name in the document front matter.
widget: Determines UI style and value data type (details below).
default (optional): Sets a default value for the field.

As described above, the widget property specifies a built-in or custom UI widget for a given field. The first field in the example, layout, uses a hidden widget. This widget will not show in the editor UI, but will be saved with the default value (assuming it’s been set) in the document front matter. The rest of the widgets work as follows:
Widget     UI     Data Type
string     text input     string
datetime     date picker widget     ISO date string
image     file picker widget with drag-and-drop     file path saved as string, image uploaded to media folder
number     text input with + and – buttons     number
markdown     rich text editor with raw option     markdown-formatted string

Based on this example, you can go through the post types in your site and add the appropriate settings to your config.yml file. Each post type should be listed as a separate node under the collections field.
Filter

The entries for any collection can be filtered based on the value of a single field. The example collection, below, would only show post entries with the value “en” in the language field.

collections:
– name: “posts”
label: “Post”
folder: “_posts”
filter:
field: language
value: en
fields:
– {label: “Language”, name: “language”}

Accessing the App

With your configuration complete, it’s time to try it out! Go to yoursite.com/admin and complete the login prompt to access the admin interface. To add users, simply add them as collaborators on the GitHub repo.

Happy posting!

NextJS

This guide will help you get started using Netlify CMS with NextJS.

Creating a new project

Let’s repeat some of the basics of setting up a simple NextJS project (check out nextjs.org/learn for a more detailed version).

# Create new directory and navigate into it
mkdir awesome-kitties
cd awesome-kitties

# Initialize a new project
npm init -y

# Install required dependencies
npm install --save react react-dom next

# Install webpack loader for Markdown (Use version 3+)
npm install --save-dev frontmatter-markdown-loader

# If using NextJS v11.0.0 or above, @babel/core and @babel/preset-react has to be installed as dependencies of frontmatter-markdown-loader
npm install --save-dev @babel/core @babel/preset-react

# Create folder for pages (default for NextJS), and add a index file
mkdir pages
touch pages/index.js

# Create a folder for content, and a markdown file:
mkdir content
touch content/home.md

# Create a folder for static assets
mkdir public

Next, we need to add some modifications to our package.json file to make it easier to run and deploy our new site:

{
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "export": "npm run build && next export"
  }
}

There is a lot of different ways to create and display Markdown content, but to make this as easy as possible we’ll be using a webpack-loader that enables us to load markdown files directly in our React components (frontmatter-markdown-loader).

Add the following content to your content/home.md file:

---
title: Awesome kitties
date: 2019-03-17T19:31:20.591Z
cats:
  - description: 'Maru is a Scottish Fold from Japan, and he loves boxes.'
    name: Maru (まる)
  - description: Lil Bub is an American celebrity cat known for her unique appearance.
    name: Lil Bub
  - description: 'Grumpy cat is an American celebrity cat known for her grumpy appearance.'
    name: Grumpy cat (Tardar Sauce)
---
Welcome to my awesome page about cats of the internet.

This page is built with NextJS, and content is managed in Netlify CMS

Next, we need to tell webpack how to load Markdown files. Create a new next.config.js file at the root of your project with the following content:

module.exports = {
    webpack: (cfg) => {
        cfg.module.rules.push(
            {
                test: /\.md$/,
                loader: 'frontmatter-markdown-loader',
                options: { mode: ['react-component'] }
            }
        )
        return cfg;
    }
}

Almost there! The last thing we need to do is to add some content to our pages/index.js file. With a little help of our webpack loader, we can now easily import Markdown files:

import Head from "next/head"
import { Component } from 'react'
import { attributes, react as HomeContent } from '../content/home.md';

export default class Home extends Component {
  render() {
    let { title, cats } = attributes;
    return (
      <>
        <Head>
          <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
        </Head>
        <article>
          <h1>{title}</h1>
          <HomeContent />
          <ul>
            {cats.map((cat, k) => (
              <li key={k}>
                <h2>{cat.name}</h2>
                <p>{cat.description}</p>
              </li>
            ))}
          </ul>
        </article>
      </>
    )
  }
}

Great! We now have a page that displays content from our Markdown file. Go ahead and start your development server to test if everything is working:

npm run dev

Adding Netlify CMS

There are many different ways to add Netlify CMS to your project. The easiest is probably just to embed it from a CDN, and that’s exactly what we’re gonna do. To avoid making this guide too complicated, we’re just going to add Netlify into a subfolder inside the /public directory (which is just served as static files by Next):

# Create and navigate into public/admin folder
mkdir -p public/admin
cd public/admin

# Create index.html and config.yml file
touch index.html
touch config.yml

Paste HTML for Netlify CMS into your public/admin/index.html file (check out the Add Netlify To Your Site section for more information)

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Content Manager</title>
  <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</head>
<body>
  <!-- Include the script that builds the page and powers Netlify CMS -->
  <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
</body>
</html>

Notice that we also added the identity widget. This allows sign up when the project is hosted at Netlify.

Paste the following configuration into your public/admin/config.yml file:

backend:
  name: git-gateway
  branch: main # Branch to update (optional; defaults to master)
media_folder: public/img
public_folder: img
collections:
  - name: "pages"
    label: "Pages"
    files:
    - label: "Home"
      name: "home"
      file: "content/home.md"
      fields:
        - { label: "Title", name: "title", widget: "string"}
        - { label: "Publish Date", name: "date", widget: "datetime" }
        - { label: "Body", name: "body", widget: "markdown"}
        - label: 'Cats'
          name: "cats"
          widget: list
          fields:
            - { label: "Name", name: "name", widget: "string"}
            - { label: "Description", name: "description", widget: "text"}

Awesome! Netlify CMS should now be available at localhost:3000/admin/index.html. Unfortunately we can’t edit our content just yet. First we need to move our code into a git repository, and create a new Netlify site.

Tip: If you want to test changes made to your config.yml file locally, swap out “git-gateway” with “test-repo” and navigate to localhost:3000/admin/index.html to view Netlify CMS locally (you can’t make changes or read actual content from Git this way, but it’s great to verify how things will look).

Publishing to GitHub and Netlify

Create a new repository at GitHub (or one of the other supported git services) and follow the instructions on how to push existing files into your new repository.

Now is probably also a good time to add a .gitignore file:

.next/
node_modules/
/npm-debug.log
.DS_Store
out/

When your project is under version control, go to Netlify and select “New Site from Git”. Select GitHub (or whatever service you used in the previous step), and the repository you just pushed to.

Under the final step (Build options, and deploy!), make sure you enter the following:

Field Value
Build command npm run export
Publish directory out

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:

  1. Go to Settings > Identity, and select Enable Identity service.
  2. Under Registration preferences, select Open or Invite only. In most cases, you want only invited users to access your CMS, but if you’re just experimenting, you can leave it open for convenience.
  3. 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.
  4. 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’re leaving the Roles field blank, which means any logged in user may access the CMS. For information on changing this, check the Netlify Identity documentation.

Celebrate!

Great job – you did it! Open your new page via the new Netlify URL, and navigate to /admin. If you did everything correct in the previous step, you should now be able to sign up for an account, and log in.

Tip: Signing up with an external provider is the easiest. If you want to sign up by email, you’ll have to set up a redirect in your index.js page (which we won’t be covering in this guide). For more information, have a look at the Add To Your Site section.

Congratulations – you can finally manage your new list of cats!

React Hooks support in Netlify CMS (and the Gatsby plugin)

Netlify CMS is an extensible app written in, and bundled with, React. The most common extension is the custom preview template, which allows the preview on the right side of the editor to show what the site will actually look like as you type. These preview templates are also written in React.

Preview templates and other extensions can only use the Netlify CMS bundled copy of React via the createClass() method that Netlify CMS exports. Since React components are most often written in JSX and transpiled through a build system, most developers won’t want to use this method, so the preview templates are created with a separate copy of React.

This means that Netlify CMS has two instances of React running at once.

But everything still worked – until React Hooks was released.

The problem: React Hooks

Before Hooks, multiple instances of React could work on the same DOM, although it was warned against and technically not supported. React Hooks changed this by throwing an error if multiple instances are detected. When developers started using hooks in their Netlify CMS previews and widgets, their applications stopped working 😭

The solution: netlify-cms-app

In the past, the netlify-cms npm package was the only way to run Netlify CMS – it’s a “batteries included” distribution that runs as-is, bringing along React and a number of default extensions (widgets, backends, etc).

As of Netlify CMS 2.9.0, a new netlify-cms-app package is provided as a slimmed down alternative to netlify-cms. It still includes most default extensions, but excludes some of the heavier ones, like media libraries for external providers.

Most importantly, it does not include react or react-dom, requiring them instead as peer dependencies. This allows Netlify CMS and any extensions to all use a single instance of React and React DOM. As a bonus, the netlify-cms-app bundle is a bit smaller than netlify-cms.

How to use it

If you’re building your site with Gatsby, skip this section. For all others, you’ll want to:

  1. Uninstall netlify-cms if you’re already using it
  2. Install netlify-cms-app
  3. Install react and react-dom
  4. Optionally install and register media libraries (see below).
npm uninstall netlify-cms
npm install netlify-cms-app react react-dom

That’s it! Now Netlify CMS will use the version of React that you provide.

The Gatsby plugin

Gatsby provides transpiling and bundling with Babel and Webpack, and accepts plugins to support various JavaScript dialects, e.g., TypeScript. If a developer sets up their Gatsby site to be written a certain way, they’ll want any CMS customization code to be written the same way. The problem is that Netlify CMS is a standalone app that would typically live in Gatsby’s static directory, and Gatsby doesn’t really have a way to handle a secondary entry point for the CMS for outputting a dedicated bundle.

For this reason, we support an official Gatsby plugin, gatsby-plugin-netlify-cms, which bundles preview templates and other Netlify CMS extensions using the same Babel and Webpack configuration as the Gatsby site itself.

Using React Hooks with Netlify CMS and Gatsby

The recent 4.0.0 release of gatsby-plugin-netlify-cms is the first to use netlify-cms-app and enable the use of React Hooks in Netlify CMS previews/widgets for Gatsby projects.

If you want to start a new site now, or would like to see an example, check out gatsby-starter-netlify-cms – it provides a great starting point and implements all of the remaining steps in this post.

You can deploy it to Netlify right now with one click!

Deploy to Netlify

Upgrading an existing project

If you’re already using gatsby-plugin-netlify-cms, you’ll want to:

  1. Upgrade to 4.0.0 or newer
  2. Remove the netlify-cms dependency
  3. Add netlify-cms-app
npm upgrade gatsby-plugin-netlify-cms@^4.0.0
npm uninstall netlify-cms
npm install --save netlify-cms-app

Note that you’ll already have React and React DOM installed in your Gatsby project, so no need to do that here.

Adding to a new project

If you’re not already using gatsby-plugin-netlify-cms with your Gatsby project, you can install it and netlify-cms-app via npm (or your package manager of choice):

npm install --save netlify-cms-app gatsby-plugin-netlify-cms

You’ll also need to register the plugin in gatsby-config.js in the site root. Create that file if it’s not already there, and add the following to register the Netlify CMS plugin:

// gatsby-config.js

module.exports = {
  plugins: [`gatsby-plugin-netlify-cms`],
}

The plugin will create the Netlify CMS app and output it to /admin/index.html on your site. The CMS will look for your configuration to be in the same directory on your live site, at /admin/config.yml, so you’ll want to place the configuration file in the static directory of your repo at static/admin/config.yml. You can also configure Netlify CMS with JavaScript. Read more about configuring the CMS in our docs, or check out the Gatsby / Netlify CMS integration guides in both our docs and theirs.

Using Media Libraries with netlify-cms-app

The Netlify CMS media library extensions for Cloudinary and Uploadcare are not included in netlify-cms-app. If you’re using netlify-cms-app, you’ll need to register media libraries yourself.

Note: if you’re using gatsby-starter-netlify-cms, the media libraries are registered within the starter itself.

import CMS from 'netlify-cms-app';

// You only need to import the media library that you'll use. We register both
// here for example purposes.
import uploadcare from 'netlify-cms-media-library-uploadcare';
import cloudinary from 'netlify-cms-media-library-cloudinary';

CMS.registerMediaLibrary(uploadcare);
CMS.registerMediaLibrary(cloudinary);

For more information about the media libraries, refer to the docs.

With gatsby-plugin-netlify-cms@^4.0.0

In addition to calling registerMediaLibrary() as mentioned above, make sure to provide the path to your CMS customization entry point to Gatsby via gatsby-config.js:

// gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-netlify-cms',
      options: {
        modulePath: `${__dirname}/src/cms/cms.js`,
      },
    },
  ]
}

If you run into a problem or need help, open an issue on GitHub or chat with our community!

Implementing a Jekyll CMS in 3 Days

This guest post was written by Shea Daniels, Lead Software Engineer at Dwolla and user of Netlify CMS. It was originally published on the Dwolla blog.

Let’s say you’re building the next great startup or putting together a spectacular event—the first question anybody asks you is “What’s the website?”

A beautiful and usable online presence is simply table stakes in 2019 for businesses, nonprofits or even prospective employees—and it was no different for Monetery, the inclusive tech summit Dwolla puts on each spring. We needed to get a great site up and running fast, so we initially landed on a reliable and proven solution that we’ve used before: GitHub Pages.

This worked well early on as we launched the Monetery homepage, but it became clear that we needed a more complete solution. Because of our robust controls process, engineering was quickly becoming a roadblock. We needed to do a better job of enabling our content editors to move fast and make necessary changes quickly.

So we took a look at our options:

  1. Implement a traditional Content Management System (CMS) like WordPress
  2. Find a Headless CMS to integrate into a Static Site Generator (SSG)

The landscape of potential products for both of these options is monumental. We were familiar with traditional options, so we scoured headlesscms.org and staticgen.com to see what else was out there. Dwolla affords its engineering staff with dedicated time for professional development each week, which gave us an opportunity to test drive potential solutions.

One of the most interesting solutions we tried came from a company called Netlify, and their project Netlify CMS.

We thought Netlify CMS might benefit us for the following reasons:

  • It’s built for use with Static Site Generators so we get to keep the speed, security and scalability benefits that drew us to SSGs in the first place
  • It’s SSG agnostic, so it would work with our existing Jekyll site but not prevent us from changing our mind down the road (hi there, GatsbyJS!)
  • There is no database backend since content changes are stored as Git commits – which makes InfoSec folks happy
  • It provides a simple and usable editor experience
  • It’s open source, so there is no vendor lock-in, and we can contribute features that are important to us back to the community

With buy-in from our stakeholders, we decided to move forward. We’ll talk about the decisions we had to make and show you how to integrate Netlify CMS with Jekyll on your own site.

Should you move from GitHub Pages to Netlify Hosting?

This was the first choice we needed to make. Switching seemed like it would add additional time and complexity to our project, and thus initially our decision was “no.” Using Netlify CMS with your existing hosting provider is a perfectly valid choice.

So why did we change our mind and move to Netlify hosting? The answer is that we found two features very compelling: Git Gateway and branch deploys.

Git Gateway works as an intermediary between the CMS and your Git repository. In concrete terms, this means you can do things like have your users log into the CMS admin with Google instead of requiring them to each have a GitHub account. Netlify then makes commits on your behalf using a GitHub account that granted access to a repo via OAuth. Although the Git Gateway is open source software as well, it was clear that learning to host that ourselves was going to involve a considerable learning curve.

Branch deploys give you the ability to have more than one version of your site live at a time. In comparison, GitHub Pages has a serious limitation in that only a single branch (usually master or gh-pages) can be deployed. This may not sound particularly exciting, but it enables a wonderful feature that we’ll get back to in a bit.

Migrating from GitHub Pages to Netlify

In general, publishing your site from Netlify is as easy as creating a Netlify account, signing in to your Git provider (GitHub, GitLab or Bitbucket) and selecting a repo. As soon as you provide a build command, Netlify can start deploying your site. Tasks like setting up SSL are explained by the Netlify Docs so we won’t cover that here.

If you were using the built-in Jekyll gems and build process that GitHub provided, you’ll need a few additional things to get the build working. You’ll need a Gemfile for your dependencies, and it’s also a good idea to check your build command into source control as well:

Gemfile
source "https://rubygems.org"
gem 'github-pages'
netlify.toml
[build]
publish = "_site/"
command = "jekyll build"

Once you’re satisfied that everything looks good and is deploying correctly from Netlify, you can proceed to claim your domain name on Netlify and migrate DNS over to Netlify’s name servers. After your DNS is fully cut over, you can safely turn off the GitHub Pages site from your repo.

Adding Netlify CMS to an Existing Site

Netlify CMS itself consists of a Single Page Application built with React that lives in an admin folder on your site. For Jekyll, it goes right at the root of your project. It will contain two files:

admin
 ├ index.html
 └ config.yml

The Netlify CMS Docs explain this better than we can:

The first file, admin/index.html, is the entry point for the Netlify CMS admin interface. This means that users navigate to yoursite.com/admin/ to access it. On the code side, it’s a basic HTML starter page that loads the Netlify CMS JavaScript file. In this example, we pull the file from a public CDN:

admin/index.html
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Content Manager</title>

  <script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</head>
<body>
  <!-- Include the script that builds the page and powers Netlify CMS -->
  <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
</body>
</html>

The second file, admin/config.yml, is the heart of your Netlify CMS installation, and a bit more complex. The Configuration section covers the details.

To start with, the config file might look something like this:

admin/config.yml
backend:
  name: git-gateway
  branch: master
  identity_url: "https://yoursite.com/.netlify/identity"
  gateway_url: "https://yoursite.com/.netlify/git"
  squash_merges: true

publish_mode: editorial_workflow
media_folder: "assets/img/uploads"

site_url: https://yoursite.com
logo_url: https://yoursite.com/assets/img/logo.svg

collections:

The backend section covers the basics like which branch to update and sets up the Git Gateway connection that we talked about earlier. The publish_mode property sets up our workflow to use the editorial mode. In short, this means that we have the ability to save page drafts as pull requests in Git before we decide to publish them. Combined with the branch deploys feature of Netlify, this is going to give us live previews of unpublished content from a static site generator!

Note: as of May 2019, the editorial workflow is only supported when you use GitHub as a provider

Now we just need to drop in the Netlify Identity Widget on the main site. This is needed because after a user logs in they’ll be redirected to the homepage of the site. We need to redirect them back to the CMS admin, so add the following script before the closing body tag:

<script>
  if (window.netlifyIdentity) {
    window.netlifyIdentity.on("init", user => {
      if (!user) {
        window.netlifyIdentity.on("login", () => {
          document.location.href = "/admin/";
        });
      }
    });
  }
</script>

With this in place, and the appropriate authentication and Git Gateway configuration on netlify.com, you should be able to log into the Netlify CMS admin for your site at https://yourdomain.com/admin.

What are Collections?

Although at this point you can log in, you can’t do much yet! There is no data structure set up for the CMS fields you’ll need to edit your site. You may have noticed the empty collections field in the config file, and this is where the magic happens. All fields for data that you want to save need to be part of a collection.

There are two types of collections, folder collections and file collections. To understand the difference, let’s figure out what Netlify CMS actually does when you make a content edit: the data has to be stored somewhere and we know that it uses Git as a back end. That means the data you save must end up inside of a file in your project. So when we configure a collection, we are telling Netlify CMS about the structure and naming convention of the files we want to create. It’s then up to your static site generator to determine how to interpret these files and pull the data into templates. In this blog post, we’ll cover how that works for Jekyll.

Knowing this, can you guess why there are two types of collections? In the case of defined options, we can tell the CMS to put that field in a specific file in our project. In the case of repeating content, like blog posts or pages built out of modular components, we want to set up Netlify CMS so that it can generate many files based on a pattern that we define. We can generate a number of different file formats too – it supports YAML, JSON, markdown with front matter, and a few others.

Setting Up a File Collection for Global Options

A file collection is the perfect place to define data fields for things that are valid across your entire site, such as global navigation, footers, and defaults. Let’s look at a file collection from a real config file:

admin/config.yml
collections:
- label: "Sitewide Options"
  name: options
  editor:
    preview: false
  files:
    - label: "Navigation Menu"
      name: nav
      file: "_data/nav.yml"
      fields:
        - label: "Nav Items"
          label_singular: "Nav Item"
          name: topLevelItems
          widget: list
          fields:
            - {label: "Display Text", name: displayText, widget: string}
            - {label: URL, name: url, widget: string}
            - label: "Item Type"
              name: itemType
              widget: select
              options: ["Link", "Button"]

This will define a new collection that shows up on left side of the CMS admin UI, and it will make a “Navigation Menu” page underneath that collection. Inside are fields that define some site navigation items that each include a name, URL, etc. We define the data type and editor interface of the fields using widgets. When a change is made, it will be saved to the file located at _data/nav.yml in your project.

Here’s an example of what the data file might look like:

_data/nav.yml
topLevelItems:
- displayText: 'A Page'
  itemType: Link
  url: /a-page/
- displayText: 'External Link'
  itemType: Link
  url: '/https://google.com'

How to Use a File Collection in Jekyll

Let’s figure out how to pull this data into a template in Jekyll. Here’s a simple liquid template that uses our nav data:

<ul>
  {% for item in site.data.nav.topLevelItems %}
    <li>
      {% if item.itemType == 'Link' %}
        <a href="{{ item.url }}">{{ item.displayText }}</a>
      {% else %}
        ...
      {% endif %}
    </li>
  {% endfor %}
</ul>

In Jekyll, everything in the _data folder is available using the site.data.{file}.{field} syntax. You can loop and get fields as you would expect.

Setting Up a Folder Collection for Pages

A folder collection is used any time we need a number of files to be generated according to a pattern, but we don’t know how many. For example, if you’re building a blog, this is what you need for your posts. In this example, we’ll use it with a cool Jekyll feature to let content editors create the pages of our site on the fly and at any path they want.

Let’s look at the bones of a folder collection from a real config file to see how this works:

admin/config.yml
collections:
- label: "Pages"
  label_singular: "Page"
  name: pages
  folder: "_pages"
  create: true
  slug: "{{slug}}"
  preview_path: "{{permalink}}"
  editor:
    preview: false
  fields:
    - {label: "Title", name: title, widget: string}
    - {label: "Permalink", name: permalink, widget: string}
    - label: "Layout Template"
      name: "layout"
      widget: "select"
      default: "blocks"
      options:
        - { label: "Default", value: "blocks" }
        - { label: "Home Page", value: "home" }
    - {label: "Meta Description", name: metaDescription, widget: text, required: false}
    - label: "Social Sharing"
      name: social
      widget: object
      required: false
      fields:
        - {label: "OpenGraph Image", name: ogImage, widget: image, required: false}
        - {label: "Twitter Image", name: twitterImage, widget: image, required: false}

This defines another new collection called “Pages” that will consist of many files all stored in the /_pages/ folder of your project. The files will be named according to the pattern in the slug field, which we’ve confusingly set to have a pattern of {{slug}}. Don’t worry, in this case it just means we’ll be using the default value, which is the contents of the title field. You can configure this in many ways to include dates and other things to match your intended use, but this is perfect for our case.

Of special note are the permalink and preview_path fields. We’ll use the permalink field to define the path of our page in Jekyll, and the preview field shares that definition with Netlify CMS so it knows how to link to the correct page preview (branch deploys FTW).

Here’s an example of what the data file for a page might look like:

_pages/home.md
---
Title: Home
permalink: /
layout: home
metaDescription: Shout out what you’re about!
social: {}
---

How to Use a Folder Collection in Jekyll

If you were reading closely, you may have noticed that the file collection is generating YAML files, while the folder collection is generating markdown files with front matter. You might think that’s a bit odd to have a markdown file with no content below the data in the front matter (demarcated by the triple dashes), but rest assured there’s a good reason!

We’ll work in concert with Jekyll’s own collections feature to pair our markdown files with a template, read the data in the front matter and then use it to generate our page output. This lets us do neato things later like use the variable type list widget to make a component based page builder!

Before we start, we need to make an addition to the Jekyll config file:

_config.yml
collections:
pages:
  output: true

This tells Jekyll to generate a new page for each markdown file in the pages folder.

But how does Jekyll know which template to use? In this case, the layout field we defined in Netlify CMS is doing exactly that. Jekyll maps the value in that front matter field directly to the name of a template file in the _layouts folder of your project.

Let’s look at an example layout template:

_layouts/home.html
---
layout: default
---

<h1>{{ page.title }}</h1>

<section class="home">
  {{ content }}
</section>

All of the data we are interested in from the front matter is available using the {collection}.{field} syntax that Jekyll provides. We’re able to use parent templates and all of the other features as you’d expect.

Making a Page Builder in Jekyll

We’re off to a great start, but we didn’t need to go to all that trouble with our folder collection if we weren’t going to take it one step farther: let’s make a flexible, component-based page builder!

First, we need to define our components in the Netlify CMS config file:

_admin/config.yml
collections:
  - label: "Pages"
    ...
    - label: "Content Blocks"
      label_singular: "Content Block"
      name: blocks
      widget: list
      types:
        - label: "Hero"
          name: hero
          widget: object
          fields:
            - {label: "Heading", name: heading, widget: string}
            - {label: "Content", name: content, widget: markdown, buttons: ["bold", "italic", "link"], required: false}
        - label: "Rich Text Block"
          name: textBlock
          widget: object
          fields:
            - {label: "Heading", name: heading, widget: string, required: false}
            - {label: "Content", name: content, widget: markdown}
        ...

Here we’ve extended our pages collection to include a variable type list widget that contains several different types of objects that the content editor will be able to dynamically add and rearrange from the CMS Admin.

Now let’s make a new layout to render our widgets:

_layouts/blocks.html
---
layout: default
---

{% for block in page.blocks %}
  {% include blocks/{{ block.type }}.html block=block %}
{% endfor %}  

Here we’re looping through each component on the page, and including another template file that knows how to render it. Here’s what a component template might look like:

_includes/blocks/hero.html
<header class="page-hero">
  <h1>{{ block.heading }}</h1>
  {% if block.content and block.content != '' %}
    <div class="max-width--330">
      {{ block.content | markdownify }}
    </div>
  {% endif %}
</header>

Because we passed along our block variable, everything is right where we need it. You’ll also notice we took special care to translate our markdown into HTML with markdownify since that isn’t being automatically done for us any more.

Our Experience with Netlify + Netlify CMS

Using these techniques, our engineers were able to integrate Netlify CMS into our existing Jekyll site for Monetery and launch a working CMS within a matter of days (three, to be exact). Content editors were able to onboard quickly and start publishing changes and new pages shortly after launch. During that time we also onboarded a new engineer who was able to start making meaningful contributions on their second day of work!

That said, we’re never done. We’re constantly learning from our experiences and trying to improve. Let’s take a balanced look at both the pros and cons of using Netlify + Netlify CMS:

Pros

  • Hosting on Netlify is a breeze and we haven’t experienced any issues with the site itself
  • Netlify CMS was very easy to retrofit onto an existing Jekyll project and intuitive for new engineers to learn
  • It’s easy and very useful to get a copy of your entire project, including content, and run it locally using docker<
  • The Netlify CMS interface is simple and easy to learn for content editors
  • Branch deploys and previews are amazing
  • Netlify’s free plans give you the freedom to evaluate the offering before committing
  • There is an active and very helpful community for Netlify CMS on Gitter
  • Netlify CMS is open source and welcomes contributions

Cons

  • Our content editors like the editorial workflow but don’t like the multiple steps to save and publish
  • Saving and publishing is relatively slow, sometimes up to a few seconds
  • We experience occasional—but frustrating—errors when using the CMS admin
  • Some widgets or functionality that you might be looking for, such as conditional logic for displaying fields in the admin UI, hasn’t been implemented yet
  • The CMS UI doesn’t work to save content to your machine during local development, it will always commit back to your Git repository, so be careful
  • You are better off hosting with Netlify instead of another provider if you want features like branch deploys and a hosted Git Gateway – this may incur more cost to your business

The Community & Contributing Back

The Netlify CMS community has been nothing short of wonderful to interact with, so we encourage you to reach out and give this technology a try. Dwolla also believes in linking our words with our actions, so we’re committed to giving back to the open source community. We’re happy to report that our first pull request contributing to Netlify CMS is already live!

Check out the code on GitHub: https://github.com/netlify/netlify-cms