Hugo

Introduction

This guide will walk you through how to integrate Netlify CMS with Hugo. This is a good place to start if you want to learn from the ground up how these two tools work together. If you want to get up-and-running quicker, you can use one of the pre-existing and amazing starter templates!

Getting started with Hugo

Installation

To get started with Hugo, you should first install the command line tool. If you’ve already got it installed, you can skip this step. On MacOS and Linux you can do this with:

brew install hugo

To test that it’s successfully installed, you can try this command, to get a list of Hugo’s options:

hugo help

Creating a new site

Create a new Hugo project and start it up using the following commands.

hugo new site <name-of-your-new-project>
cd <name-of-your-new-project>
hugo server

You won’t actually see anything, just yet, and that’s because you don’t have any template files. That’s easily resolved. In the layouts/ directory, create a file index.html and put a basic HTML structure in there:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <h1>Nice. It's looking good already.</h1>
  </body>
</html>

You’ll also add some files to the content/ and data/ directories to make sure git tracks them.

touch content/.keep data/.keep

This is as basic as you can get with a Hugo project. There’s just enough here now for us to install Netlify CMS.

Getting Started With Netlify CMS

Add the Netlify CMS files to Hugo

In Hugo, static files that don’t need to be processed by the build commands live in the static/ directory. You’ll install the Netlify CMS admin and config files there. Create a directory admin/ and within it, create two files index.html and config.yml. In the index.html, add the following content:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Content Manager</title>
    <!-- Include the script that enables Netlify Identity on this page. -->
    <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>

In the config.yml file, you can add this basic configuration — you can customize as you see fit, this sample file is just to get you started.

backend:
  name: git-gateway
  branch: main # Branch to update (optional; defaults to master)
media_folder: static/img
public_folder: /img
collections:
  - name: 'blog'
    label: 'Blog'
    folder: 'content/blog'
    create: true
    slug: '{{year}}-{{month}}-{{day}}-{{slug}}'
    editor:
      preview: false
    fields:
      - { label: 'Title', name: 'title', widget: 'string' }
      - { label: 'Publish Date', name: 'date', widget: 'datetime' }
      - { label: 'Description', name: 'description', widget: 'string' }
      - { label: 'Body', name: 'body', widget: 'markdown' }

Note: You won’t be able to access the CMS just yet — you still need to deploy the project with Netlify and authenticate with Netlify Identity. You’ll handle this in the next few steps of this guide.

Pushing to GitHub

It’s now time to commit your changes and push to GitHub. You can run the following commands to initialize a git repository and push the changes so far.

git init # Initialize a git repository
git add . # Add every file
git commit -m "Initial Commit" # Commit every file with the message 'Initial Commit'
git remote add origin https://github.com/YOUR_USERNAME/NEW_REPO_NAME.git # Create a new repo on GitHub and add it to this project as a remote repository.
git push -u origin main # Push your changes

Deploying With Netlify

Now you can go ahead and deploy to Netlify. Go to your Netlify dashboard and click New site from Git. Select the repo you just created. Under Basic build settings, you can set the build command to hugo and the publish directory to public. Click Deploy site to get the process going.

Authenticating with Netlify Identity

Add the Netlify Identity Widget

You’ve already added the Netlify Identity widget to our admin/index.html. The next thing to do is add the Netlify Identity widget to our site’s index page. In layouts/index.html, we can add the following to the <head> tag on the page:

<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>

Once you’ve added this, make sure to push your changes to GitHub!

Enable Identity & Git Gateway in Netlify

Back in your Netlify dashboard:

  1. Go to Settings > Identity, and select Enable Identity service.
  2. Once enabled, select Settings and usage, and scroll down to Registration preferences. You can set this to either Open or Invite only, but usually Invite only is your best bet for a personal site.
  3. If you don’t want to create an account, or would like to use an external provider such as GitHub or Google, you can enable those services under External providers.
  4. Scroll down to Services and click Enable Git Gateway.

Accessing the CMS

Once you’ve reached this point, you should be able to access the CMS in your browser at http://localhost:1313/admin. You’ll be prompted to add the URL of your Netlify site. Once you’ve added that URL, you can log in with an Identity account or with one of the External Providers you enabled in step 3 above. For the sake of this tutorial, you can create a blog post in the CMS, and publish it! Once you git pull in your project, the blog post will show up in the project at content/blog/<slugified-blog-post-title>.md.

And that’s it! From this point on, it’s just a matter of following the Hugo documentation for outputting the content from your content/ directory into templates! For more information on configuring Netlify CMS, feel free to check out the Netlify CMS configuration options documentation.

Using Netlify CMS content in Hugo

Creating a list of posts

In your layouts/index.html file, you’ll create an unordered list element and use a Hugo range to output all posts. Inside that range, you can add a list item element with each post title and a link to the post inside it.

Note: To learn more about Hugo’s range function, check out the Hugo documentation.

<body>
  <h1>Nice. It's looking good already.</h1>
  <ul>
    {{ range (where .Pages "Section" "blog") }}
    <li>
      <a href="{{ .RelPermalink }}">
        {{ .Title }}
      </a>
    </li>
    {{ end }}
  </ul>
</body>

That link won’t work just right just yet. You’ll need to make a single page layout for blog posts, so Hugo can create a page for the .RelPermalink to link to.

Creating a single page post layout

Create a file layouts/blog/single.html, and put the following content in there:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>{{ .Title }}</title>
  </head>
  <body>
    <h1>{{ .Title }}</h1>
    <p class="date">{{ .Date }}</p>
    <p class="description">{{ .Params.description }}</p>
    <article class="content">
      {{ .Content }}
    </article>
  </body>
</html>

You can see this basic template includes all the fields you’ve specified in your Netlify CMS config.yml file. You can access any custom front-matter fields with .Params.<field-name>!

Using Hugo shortcodes in the Markdown Editor

Using registerEditorComponent we can register a block level component for the Markdown editor. You can use it to add Hugo’s inbuilt shortcodes like gist,youtube and others as block components to the markdown editor.

You can refer to registering editor components for a getting started guide or for creating your own editor components.

Example

CMS.registerEditorComponent({
    id: "gist",
    label: "Gist",
    fields: [{
            name: "username",
            label: "Github Username",
            widget: "string"
        },
        {
            name: "gid",
            label: "Gist ID",
            widget: "string"
        },
    ],
    pattern: /^{{< gist ([a-zA-Z0-9]+) ([a-zA-Z0-9]+) >}}/,
    fromBlock: function(match) {
        return {
            username: match[1],
            gid: match[2],
        };
    },
    toBlock: function(obj) {
        return `{{< gist ${obj.username} ${obj.gid} >}}`;
    },
    toPreview: function(obj) {
        return '<a href="https://gist.github.com/' + obj.username + '/' + obj.id + '">gist</a>';
    },
});

Result

Gist

For getting started quickly you can refer to this amazing prebuilt resource of hugo shortcodes editor components!

Creating Custom Widgets

The NetlifyCMS exposes a window.CMS a global object that you can use to register custom widgets, previews, and editor plugins. The same object is also the default export if you import Netlify CMS as an npm module. The available widget extension methods are:

  • registerWidget: registers a custom widget.
  • registerEditorComponent: adds a block component to the Markdown editor.

Writing React Components inline

The registerWidget requires you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.

However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, NetlifyCMS exposes two constructs globally to allow you to create components inline: ‘createClass’ and ‘h’ (alias for React.createElement).

registerWidget

Register a custom widget.

// Using global window object
CMS.registerWidget(name, control, [preview], [schema]);

// Using npm module import
import CMS from 'netlify-cms';
CMS.registerWidget(name, control, [preview], [schema]);

Params:

Param Type Description
name string Widget name, allows this widget to be used via the field widget property in config
control React.Component or string
  • React component that renders the control, receives the following props:
    • value: Current field value
    • field: Immutable map of current field configuration
    • forID: Unique identifier for the field
    • classNameWrapper: class name to apply CMS styling to the field
    • onChange: Callback function to update the field value
  • Name of a registered widget whose control should be used (includes built in widgets).
[preview] React.Component, optional Renders the widget preview, receives the following props:

  • value: Current preview value
  • field: Immutable map of current field configuration
  • metadata: Immutable map of any available metadata for the current field
  • getAsset: Function for retrieving an asset url for image/file fields
  • entry: Immutable Map of all entry data
  • fieldsMetaData: Immutable map of metadata from all fields.
[schema] JSON Schema object, optional Enforces a schema for the widget’s field configuration

Example:

admin/index.html

<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
<script>
var CategoriesControl = createClass({
  handleChange: function(e) {
    const separator = this.props.field.get('separator', ', ')
    this.props.onChange(e.target.value.split(separator).map((e) => e.trim()));
  },

  render: function() {
    const separator = this.props.field.get('separator', ', ');
    var value = this.props.value;
    return h('input', {
      id: this.props.forID,
      className: this.props.classNameWrapper,
      type: 'text',
      value: value ? value.join(separator) : '',
      onChange: this.handleChange,
    });
  },
});

var CategoriesPreview = createClass({
  render: function() {
    return h('ul', {},
      this.props.value.map(function(val, index) {
        return h('li', {key: index}, val);
      })
    );
  }
});

var schema = {
  properties: {
    separator: { type: 'string' },
  },
}

CMS.registerWidget('categories', CategoriesControl, CategoriesPreview, schema);
</script>

admin/config.yml

collections:
  - name: posts
    label: Posts
    folder: content/posts
    fields:
      - name: title
        label: Title
        widget: string
      - name: categories
        label: Categories
        widget: categories
        separator: __

registerEditorComponent

Register a block level component for the Markdown editor:

CMS.registerEditorComponent(definition)

Params

  • definition: The component definition; must specify: id, label, fields, patterns, fromBlock, toBlock, toPreview

Additional properties are optional and will be passed to the underlying widget control (object widget by default). For example, adding a collapsed: true property will collapse the widget by default.

Example:

<script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
<script>
CMS.registerEditorComponent({
  // Internal id of the component
  id: "collapsible-note",
  // Visible label
  label: "Collapsible Note",
  // Fields the user need to fill out when adding an instance of the component
  fields: [
    {
      name: 'summary',
      label: 'Summary',
      widget: 'string'
    },
    {
      name: 'details',
      label: 'Details',
      widget: 'markdown'
    }
  ],
  // Regex pattern used to search for instances of this block in the markdown document.
  // Patterns are run in a multline environment (against the entire markdown document),
  // and so generally should make use of the multiline flag (`m`). If you need to capture
  // newlines in your capturing groups, you can either use something like
  // `([\S\s]*)`, or you can additionally enable the "dot all" flag (`s`),
  // which will cause `(.*)` to match newlines as well.
  //
  // Additionally, it's recommended that you use non-greedy capturing groups (e.g.
  // `(.*?)` vs `(.*)`), especially if matching against newline characters.
  pattern: /^<details>$\s*?<summary>(.*?)<\/summary>\n\n(.*?)\n^<\/details>$/ms,
  // Given a RegExp Match object
  // (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#return_value),
  // return an object with one property for each field defined in `fields`.
  //
  // This is used to populate the custom widget in the markdown editor in the CMS.
  fromBlock: function(match) {
    return {
      summary: match[1],
      detail: match[2]
    };
  },
  // Given an object with one property for each field defined in `fields`,
  // return the string you wish to be inserted into your markdown.
  //
  // This is used to serialize the data from the custom widget to the
  // markdown document
  toBlock: function(data) {
    return `
<details>
  <summary>${data.summary}</summary>

  ${data.detail}

</details>
`;
  },
  // Preview output for this component. Can either be a string or a React component
  // (component gives better render performance)
  toPreview: function(data) {
    return `
<details>
  <summary>${data.summary}</summary>

  ${data.detail}

</details>
`;
  }
});
</script>

Result:

![youtube-widget](/img/screen shot 2018-01-05 at 4.25.07 pm.png)

Advanced field validation

All widget fields, including those for built-in widgets, include basic validation capability using the required and pattern options.

With custom widgets, the widget control can also optionally implement an isValid method to perform custom validations, in addition to presence and pattern. The isValid method will be automatically called, and it can return either a boolean value, an object with an error message or a promise. Examples:

Boolean No errors:

  isValid = () => {
    // Do internal validation
    return true;
  };

Existing error:

  isValid = () => {
    // Do internal validation
    return false;
  };

Object with error (useful for returning custom error messages) Existing error:

  isValid = () => {
    // Do internal validation
    return { error: { message: 'Your error message.' } };
  };

Promise You can also return a promise from isValid. While the promise is pending, the widget will be marked as “in error”. When the promise resolves, the error is automatically cleared.

  isValid = () => {
    return this.existingPromise;
  };

Note: Do not create a promise inside isValidisValid is called right before trying to persist. This means that even if a previous promise was already resolved, when the user hits ‘save’, isValid will be called again. If it returns a new promise, it will be immediately marked as “in error” until the new promise resolves.

Writing custom widgets as a separate package

Widgets are inputs for the Netlify CMS editor interface. It’s a React component that receives user input and outputs a serialized value. Those are the only rules – the component can be extremely simple, like text input, or extremely complicated, like a full-blown markdown editor. They can make calls to external services, and generally do anything that JavaScript can do.

For writing custom widgets as a separate package you should follow these steps:

  1. Create a directory
    mkdir my-custom-widget
  2. Navigate to the directory
    cd my-custom-widget
  3. For setting up a new npm package run this command:
    npm init
  4. Answer the questions in the command line questionnaire.
  5. In order to build React components, we need to set up a build step. We’ll be using Webpack. Please run the following commands to install the required dependencies:
   npm install --save-dev babel-loader@7 babel-core babel-plugin-transform-class-properties babel-plugin-transform-export-extensions babel-plugin-transform-object-rest-spread babel-preset-env babel-preset-react cross-env css-loader html-webpack-plugin netlify-cms react source-map-loader style-loader webpack webpack-cli webpack-serve
   npm install --save prop-types

And you should manually add “peerDependencies” and “scripts” as shown below.

Here is the content of package.json that you will have at the end:

{
  "name": "netlify-cms-widget-starter",
  "description": "A boilerplate for creating Netlify CMS widgets.",
  "author": "name of developer",
  "keywords": [
    "netlify",
    "netlify-cms",
    "cms",
    "widget",
    "starter",
    "boilerplate"
  ],
  "version": "0.0.1",
  "homepage": "https://github.com/netlify/netlify-cms-widget-starter",
  "license": "MIT",
  "main": "dist/main.js",
  "devDependencies": {
    "babel-loader": "^7.1.4",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-export-extensions": "^6.22.0",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "cross-env": "^5.1.4",
    "css-loader": "^0.28.11",
    "html-webpack-plugin": "^3.2.0",
    "netlify-cms": "^1.5.0",
    "react": "^16.3.2",
    "source-map-loader": "^0.2.3",
    "style-loader": "^0.20.3",
    "webpack": "^4.6.0",
    "webpack-cli": "^2.0.14",
    "webpack-serve": "^0.3.1"
  },
  "dependencies": {
    "prop-types": "^15.6.1"
  },
  "peerDependencies": {
    "react": "^16"
  },
  "scripts": {
    "start": "webpack-serve --static public --open"
  }
}
  1. Create a Webpack configuration file with this content:webpack.config.js
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    const developmentConfig = {
      mode: 'development',
      entry: './dev/index.js',
      output: {
        path: path.resolve(__dirname, 'public'),
      },
      optimization: { minimize: false },
      module: {
        rules: [
          {
            test: /\.js$/,
            loader: 'source-map-loader',
            enforce: 'pre',
          },
          {
            test: /\.jsx?$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
          },
          {
            test: /\.css$/,
            use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
          },
        ],
      },
      plugins: [
        new HtmlWebpackPlugin(),
      ],
      devtool: 'eval-source-map',
    }
    
    const productionConfig = {
      mode: 'production',
      module: {
        rules: [
          {
            test: /\.jsx?$/,
            loader: 'babel-loader',
          },
        ],
      },
      devtool: 'source-map',
    }
    
    module.exports = process.env.NODE_ENV === 'production' ? productionConfig : developmentConfig
  2. The .babelrc file is our local configuration for our code in the project. You should create it under the root of the application repo. It will affect all files that Babel processes. So, create a .babelrc file under the main project with this content:
{
  "presets": [
    "react",
    "env",
  ],
  "plugins": [
    "transform-export-extensions",
    "transform-class-properties",
    "transform-object-rest-spread",
  ],
}
  1. Create a src directory with the files Control.js, Preview.js and index.js

src/Control.js

 import PropTypes from 'prop-types';
 import React from 'react';

 export default class Control extends React.Component {
   static propTypes = {
     onChange: PropTypes.func.isRequired,
     forID: PropTypes.string,
     value: PropTypes.node,
     classNameWrapper: PropTypes.string.isRequired,
   }

   static defaultProps = {
     value: '',
   }

   render() {
     const {
       forID,
       value,
       onChange,
       classNameWrapper,
     } = this.props;

     return (
       <input
         type="text"
         id={forID}
         className={classNameWrapper}
         value={value || ''}
         onChange={e => onChange(e.target.value)}
       />
     );
   }
 }

src/Preview.js

import PropTypes from 'prop-types';
import React from 'react';

export default function Preview({ value }) {
  return <div>{ value }</div>;
}

Preview.propTypes = {
  value: PropTypes.node,
};

src/index.js

import Control from './Control'
import Preview from './Preview'

if (typeof window !== 'undefined') {
  window.Control = Control
  window.Preview = Preview
}

export { Control, Preview }
  1. Now you need to set up the locale example site. Under the main project, create a dev directory with the files bootstrap.js and index.js

bootstrap.js

window.CMS_MANUAL_INIT = true

index.js

import './bootstrap.js'
import CMS, { init } from 'netlify-cms'
import 'netlify-cms/dist/cms.css'
import { Control, Preview } from '../src'

const config = {
backend: {
 name: 'test-repo',
 login: false,
},
media_folder: 'assets',
collections: [{
 name: 'test',
 label: 'Test',
 files: [{
   file: 'test.yml',
   name: 'test',
   label: 'Test',
   fields: [
     { name: 'test_widget', label: 'Test Widget', widget: 'test'},
   ],
 }],
}],
}

CMS.registerWidget('test', Control, Preview)

init({ config })

Development

To run a copy of Netlify CMS with your widget for development, use the start script:

npm start

Your widget source is in the src directory, where there are separate files for the Control and Preview components.

Production & Publishing

You’ll want to take a few steps before publishing a production built package to npm:

  1. Customize package.json with details for your specific widget, e.g. name, description, author, version, etc.
    {
      "name": "netlify-cms-widget-starter",
      "description": "A boilerplate for creating Netlify CMS widgets.",
      "author": "name of developer",
      "keywords": [
        "netlify",
        "netlify-cms",
        "cms",
        "widget",
        "starter",
        "boilerplate"
      ],
      "version": "0.0.1",
      // ... rest
    }
  2. For discoverability, ensure that your package name follows the pattern netlify-cms-widget-<name>.
  3. Delete this README.md, rename README_TEMPLATE.md to README.md, and update the new file for your specific widget.
  4. Rename the exports in src/index.js. For example, if your widget is netlify-cms-widget-awesome, you would do:
if (typeof window !== 'undefined') {
  window.AwesomeControl = Control
  window.AwesomePreview = Preview
}

export { Control as AwesomeControl, Preview as AwesomePreview }
  1. Optional: customize the component and file names in src.
  2. If you haven’t already, push your repo to your GitHub account so the source available to other developers.
  3. Create a production build, which will be output to dist:
npm run build
  1. Finally, if you’re sure things are tested and working, publish!
npm publish

GitHub

For repositories stored on GitHub, the github backend allows CMS users to log in directly with their GitHub account. Note that all users must have push access to your content repository for this to work.

Because Github requires a server for authentication, Netlify facilitates basic GitHub authentication.

To enable basic GitHub authentication:

  1. Follow the authentication provider setup steps in the Netlify docs.
  2. Add the following lines to your Netlify CMS config.yml file:
backend:
  name: github
  repo: owner-name/repo-name # Path to your GitHub repository
  # optional, defaults to master
  # branch: main

Specifying a status for deploy previews

The GitHub backend supports deploy preview links. Netlify CMS checks the context of a commit’s statuses and infers one that seems to represent a deploy preview. If you need to customize this behavior, you can specify which context to look for using preview_context:

backend:
  name: github
  repo: my/repo
  preview_context: my-provider/deployment

The above configuration would look for the status who’s "context" is "my-provider/deployment".

Identity

Authenticate users with Netlify Identity

Netlify CMS Identity service brings a full suite of authentication functionality. This allows you to manage and authenticate users on your project or app, without requiring them to be users of Netlify or any other service. You can use this for gated content, project administration, and more.
Availability

Identity is available on all credit-based plans at no additional cost. Here is the available functionality by account type:
Feature Free / Personal Pro / Enterprise
Active users Unlimited Unlimited
Invite-only users Unlimited Unlimited
Custom OAuth credentials ✓ ✓
Functions integration ✓ ✓
Custom outgoing email – ✓
Custom email templates – ✓
Identity audit log – ✓

All plans get access to:

  • Email and password signup with configurable confirmation emails.
  • External provider login with Google, GitHub, GitLab, and Bitbucket.
  • Server-side user verification in Netlify Functions and Edge Functions.
  • Role-based access control to restrict content by user role using redirect rules.
  • Admin operations to list, create, update, and delete users programmatically.
  • Identity event functions that trigger on signup, login, and validation events.
  • Account recovery and email change flows with customizable email templates.
  • Invite-only registration for private or internal sites.

To get started, add Identity to your project. You can use an agent runner to set everything up automatically, or install the @netlify/identity package manually.