Widgets

Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in widgets. Click the widget names in the sidebar to jump to specific widget details. We’re always adding new widgets, and you can also create your own!

Widgets are specified as collection fields in the Netlify CMS config.yml file. Note that YAML syntax allows lists and objects to be written in block or inline style, and the code samples below include a mix of both.

To see working examples of all of the built-in widgets, try making a ‘Kitchen Sink’ collection item on the CMS demo site. (No login required: click the login button and the CMS will open.) You can refer to the demo configuration code to see how each field was configured.

Common widget options

The following options are available on all fields:

  • required: specify as false to make a field optional; defaults to true
  • hint: optionally add helper text directly below a widget. Useful for including instructions. Accepts markdown for bold, italic, strikethrough, and links.
  • pattern: add field validation by specifying a list with a regex pattern and an error message; more extensive validation can be achieved with custom widgets.
  • Example:
      label: "Title"
      name: "title"
      widget: "string"
      pattern: ['.{12,}', "Must have at least 12 characters"]

Default widgets

Boolean

The boolean widget translates a toggle switch input to a true/false value.

  • Name: boolean
  • UI: toggle switch
  • Data type: boolean
  • Options:
    • default: accepts true or false; defaults to false when required is set to false
  • Example:
    - {label: "Draft", name: "draft", widget: "boolean", default: true}

Code

The code widget provides a code editor (powered by Codemirror) with optional syntax awareness. Can output the raw code value or an object with the selected language and the raw code value.

  • Name: code
  • UI: code editor
  • Data type: string
  • Options:
    • default_language: optional; default language to use
    • allow_language_selection: optional; defaults to false: allows syntax to be changed
    • keys: optional; sets key names for code and lang if outputting an object; defaults to { code: 'code', lang: 'lang' }
    • output_code_only: set to true to output the string value only, defaults to false
  • Example:
    - label: 'Code'
    name: 'code'
    widget: 'code'

Color

The color widget translates a color picker to a color string.

  • Name: color
  • UI: color picker
  • Data type: string
  • Options:
    • default: accepts a string; defaults to an empty string. Sets the default value
    • allowInput: accepts a boolean, defaults to false. Allows manual editing of the color input value
    • enableAlpha: accepts a boolean, defaults to false. Enables Alpha editing
  • Example:
    - { label: 'Color', name: 'color', widget: 'color' }
  • Example:
    - { label: 'Color', name: 'color', widget: 'color', enableAlpha: tru

Date

Deprecation notice: the date widget has been deprecated and will be removed in the next major release. Please use the datetime widget instead.

The date widget translates a date picker input to a date string. For saving date and time together, use the datetime widget.

  • Name: date
  • UI: date picker
  • Data type: Moment.js-formatted date string
  • Options:
    • default: accepts a date string, or an empty string to accept blank input; otherwise defaults to current date
    • format: optional; accepts Moment.js tokens; defaults to raw Date object (if supported by output format)
    • date_format: optional; boolean or Moment.js tokens. If true use default locale format.
    • time_format: optional; boolean or Moment.js tokens. If true use default locale format, false hides time-picker. Defaults to false.
  • Example:
    - label: 'Birthdate'
      name: 'birthdate'
      widget: 'date'
      default: ''
      format: 'MMM Do YY'

DateTime

The datetime widget translates a datetime picker to a datetime string.

  • Name: datetime
  • UI: datetime picker
  • Data type: Moment.js-formatted datetime string
  • Options:
    • default: accepts a datetime string, or an empty string to accept blank input; otherwise defaults to current datetime
    • format: sets storage format; accepts Moment.js tokens; defaults to raw Date object (if supported by output format)
    • date_format: sets date display format in UI; boolean or Moment.js tokens. If true use default locale format.
    • time_format: sets time display format in UI; boolean or Moment.js tokens. If true use default locale format, false hides time-picker.
    • picker_utc: (default: false) when set to true, the datetime picker will display times in UTC. When false, the datetime picker will display times in the user’s local timezone. When using date-only formats, it can be helpful to set this to true so users in all timezones will see the same date in the datetime picker.
  • Example:
    - label: "Start time"
      name: "start"
      widget: "datetime"
      default: ""
      date_format: "DD.MM.YYYY" # e.g. 24.12.2021
      time_format: "HH:mm" # e.g. 21:07
      format: "LLL"
      picker_utc: false

File

The file widget allows editors to upload a file or select an existing one from the media library. The path to the file will be saved to the field as a string.

  • Name: file
  • UI: file picker button opens media gallery
  • Data type: file path string
  • Options:
    • default: accepts a file path string; defaults to null
    • media_library: media library settings to apply when a media library is opened by the current widget
      • allow_multiple: (default: true) when set to false, prevents multiple selection for any media library extension, but must be supported by the extension in use
      • config: a configuration object that will be passed directly to the media library being used – available options are determined by the library
      • media_folder (Beta): file path where uploaded files will be saved specific to this control. Paths can be relative to a collection folder (e.g. files will add the file to a sub-folder in the collection folder) or absolute with reference to the base of the repo which needs to begin with / (e.g /static/files will save uploaded files to the static folder in a sub folder named files)
      • choose_url: (default: true) when set to false, the “Insert from URL” button will be hidden
  • Example:
    - label: "Manual PDF"
      name: "manual_pdf"
      widget: "file"
      default: "/uploads/general-manual.pdf"
      media_library:
        config:
          multiple: true

Hidden

Hidden widgets do not display in the UI. In folder collections that allow users to create new items, you will often want to set a default for hidden fields, so they will be set without requiring an input.

  • Name: hidden
  • UI: none
  • Data type: any valid data type
  • Options:
    • default: accepts any valid data type; recommended for collections that allow adding new items
  • Example:
    - {label: "Layout", name: "layout", widget: "hidden", default: "blog"

Image

The image widget allows editors to upload an image or select an existing one from the media library. The path to the image file will be saved to the field as a string.

  • Name: image
  • UI: file picker button opens media gallery allowing image files (jpg, jpeg, webp, gif, png, bmp, tiff, svg) only; displays selected image thumbnail
  • Data type: file path string
  • Options:
    • default: accepts a file path string; defaults to null
    • media_library: settings to apply when a media library is opened by the current widget
    • allow_multiple: (default: true) when set to false, multiple selection will be disabled even if the media library extension supports it
    • config: a configuration object passed directly to the media library; check the documentation of your media library extension for available config options
    • media_folder (Beta): file path where uploaded images will be saved specific to this control. Paths can be relative to a collection folder (e.g. images will add the image to a sub-folder in the collection folder) or absolute with reference to the base of the repo which needs to begin with / (e.g /static/images will save uploaded images to the static folder in a sub folder named images)
    • choose_url: (default: true) when set to false, the “Insert from URL” button will be hidden
  • Example:
  - label: "Featured Image"
    name: "thumbnail"
    widget: "image"
    choose_url: true
    default: "/uploads/chocolate-dogecoin.jpg"
    media_library:
      config:
        multiple: true

List

The list widget allows you to create a repeatable item in the UI which saves as a list of widget values. map a user-provided string with a comma delimiter into a list. You can choose any widget as a child of a list widget—even other lists.

  • Name: list
  • UI: without any fields specified, the list widget defaults to a text input for entering comma-separated values; with fields specified, the list widget contains a repeatable child widget, with controls for adding, deleting, and re-ordering the repeated widgets.
  • Data type: list of widget values
  • Options:
    • default: you may specify a list of strings to populate the basic text field, or an array of list items for lists using the fields option. If no default is declared when using field or fields, will default to a single list item using the defaults on the child widgets
    • allow_add: false hides the button to add additional items
    • collapsed: when true, the entries collapse by default
    • summary: specify the label displayed on collapsed entries
    • minimize_collapsed: when true, collapsing the list widget will hide all of it’s entries instead of showing summaries
    • label_singular: the text to show on the add button
    • field: a single widget field to be repeated
    • fields: a nested list of multiple widget fields to be included in each repeatable iteration
    • max: maximum number of items in the list
    • min: minimum number of items in the list
    • add_to_top: when true, new entries will be added to the top of the list
  • Example (field/fields not specified):
- label: "Tags"
  name: "tags"
  widget: "list"
  default: ["news"]
  • Example (allow_add marked false):
- label: "Tags"
  name: "tags"
  widget: "list"
  allow_add: false
  default: ["news"]
  • Example (with field):
- label: "Gallery"
  name: "galleryImages"
  widget: "list"
  summary: '{{fields.image}}'
  field: {label: Image, name: image, widget: image}
  • Example (with fields):
- label: "Testimonials"
  name: "testimonials"
  widget: "list"
  summary: '{{fields.quote}} - {{fields.author.name}}'
  fields:
    - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
    - label: Author
      name: author
      widget: object
      fields:
        - {label: Name, name: name, widget: string, default: "Emmet"}
        - {label: Avatar, name: avatar, widget: image, default: "/img/emmet.jpg"}
  • Example (with default):
- label: "Gallery"
  name: "galleryImages"
  widget: "list"
  fields:
    - { label: "Source", name: "src", widget: "string" }
    - { label: "Alt Text", name: "alt", widget: "string" }
  default:
    - { src: "/img/tennis.jpg", alt: "Tennis" }
    - { src: "/img/footbar.jpg", alt: "Football" }
  • Example (collapsed marked false):
- label: "Testimonials"
  name: "testimonials"
  collapsed: false
  widget: "list"
  fields:
    - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
    - {label: Author, name: author, widget: string }
  • Example (minimize_collapsed marked true):
- label: "Testimonials"
  name: "testimonials"
  minimize_collapsed: true
  widget: "list"
  fields:
    - {label: Quote, name: quote, widget: string, default: "Everything is awesome!"}
    - {label: Author, name: author, widget: string }
  • Example (with max & min):
- label: "Tags"
  name: "tags"
  widget: "list"
  max: 3
  min: 1
  default: ["news"]
  • Example (add_to_top marked true):
- label: "Tags"
  name: "tags"
  widget: "list"
  add_to_top: true

Map

The map widget allows you to edit spatial data using an interactive map. Spatial data for a single piece of geometry saves as a GeoJSON string in WGS84 projection.

  • Name: map
  • UI: interactive map
  • Data type: GeoJSON string
  • Options:
    • decimals: accepts a number to specify precision of saved coordinates; defaults to 7 decimals
    • default: accepts a GeoJSON string containing a single geometry; defaults to an empty string
    • type: accepts one string value of Point, LineString or Polygon; defaults to Point
  • Example:
    - {label: "Location", name: "location", widget: "map" }

Markdown

The markdown widget provides a full fledged text editor allowing users to format text with features such as headings and blockquotes. Users can change their editing view with a handy toggle button.

Please note: If you want to use your markdown editor to fill a markdown file contents after its frontmatter, you’ll have to name the field body so the CMS recognizes it and saves the file accordingly.

  • Name: markdown
  • UI: full text editor
  • Data type: markdown
  • Options:
    • default: accepts markdown content
    • minimal: accepts a boolean value, false by default. Sets the widget height to minimum possible.
    • buttons: an array of strings representing the formatting buttons to display (all shown by default). Buttons include: bold, italic, code, link, heading-one, heading-two, heading-three, heading-four, heading-five, heading-six, quote, bulleted-list, and numbered-list.
    • editor_components: an array of strings representing the names of editor components to display (all shown by default). Netlify CMS includes image and code-block editor components by default, and custom components may be created and registered.
    • modes: an array of strings representing the names of allowed editor modes. Possible modes are raw and rich_text. A toggle button appears in the toolbar when more than one mode is available.
    • sanitize_preview: accepts a boolean value, false by default. Sanitizes markdown preview to prevent XSS attacks – might alter the preview content.
  • Example:
    - { label: 'Blog post content', name: 'body', widget: 'markdown' }

This would render as:

Markdown widget example

Please note: The markdown widget outputs a raw markdown string. Your static site generator may or may not render the markdown to HTML automatically. Consult with your static site generator’s documentation for more information about rendering

Number

The number widget uses an HTML number input, saving the value as a string, integer, or floating point number.

  • Name: number
  • UI: HTML number input
  • Data type: string by default; configured by value_type option
  • Options:
    • default: accepts string or number value; defaults to empty string
    • value_type: accepts int or float; any other value results in saving as a string
    • min: accepts a number for minimum value accepted; unset by default
    • max: accepts a number for maximum value accepted; unset by default
    • step: accepts a number for stepping up/down values in the input; 1 by default
  • Example:
    - label: "Puppy Count"
      name: "puppies"
      widget: "number"
      default: 2
      value_type: "int"
      min: 1
      max: 101
      step: 2

markdown.

Object

The object widget allows you to group multiple widgets together, nested under a single field. You can choose any widget as a child of an object widget—even other objects.

  • Name: object
  • UI: a field containing one or more child widgets
  • Data type: list of child widget values
  • Options:
    • default: you can set defaults within each sub-field’s configuration
    • collapsed: if added and labeled true, collapse the widget’s content by default
    • summary: specify the label displayed when the object is collapsed
    • fields: (required) a nested list of widget fields to include in your widget
  • Example:
    - label: "Profile"
      name: "profile"
      widget: "object"
      summary: '{{fields.name}}: {{fields.birthdate}}'
      fields:
        - {label: "Public", name: "public", widget: "boolean", default: true}
        - {label: "Name", name: "name", widget: "string"}
        - label: "Birthdate"
          name: "birthdate"
          widget: "date"
          default: ""
          format: "MM/DD/YYYY"
        - label: "Address"
          name: "address"
          widget: "object"
          collapsed: true
          fields: 
            - {label: "Street Address", name: "street", widget: "string"}
            - {label: "City", name: "city", widget: "string"}
            - {label: "Postal Code", name: "post-code", widget: "string"

Relation

The relation widget allows you to reference items from another collection. It provides a search input with a list of entries from the collection you’re referencing, and the list automatically updates with matched entries based on what you’ve typed.

  • Name: relation
  • UI: text input with search result dropdown
  • Data type: data type of the value pulled from the related collection item
  • Options:
    • collection: (required) name of the referenced collection (string)
    • value_field: (required) name of the field from the referenced collection whose value will be stored for the relation. For nested fields, separate each subfield with a . (e.g. name.first). For list fields use a wildcard * to target all list items (e.g. categories.*).
    • search_fields: (required) list of one or more names of fields in the referenced collection to search for the typed value. Syntax to reference nested fields is similar to that of value_field.
    • file: allows referencing a specific file when the referenced collection is a files collection (string)
    • display_fields: list of one or more names of fields in the referenced collection that will render in the autocomplete menu of the control. Defaults to value_field. Syntax to reference nested fields is similar to that of value_field.
    • default: accepts any widget data type; defaults to an empty string
    • multiple : accepts a boolean, defaults to false
    • min: minimum number of items; ignored if multiple is false
    • max: maximum number of items; ignored if multiple is false
    • options_length: accepts integer to override number of options presented to user. Defaults to 20.
  • Referencing a folder collection example (assuming a separate “authors” collection with “name” and “twitterHandle” fields with subfields “first” and “last” for the “name” field):
- label: "Post Author"
  name: "author"
  widget: "relation"
  collection: "authors"
  search_fields: ["name.first", "twitterHandle"]
  value_field: "name.first"
  display_fields: ["twitterHandle", "followerCount"]

The generated UI input will search the authors collection by name and twitterHandle, and display each author’s handle and follower count. On selection, the author’s name is saved for the field.

  • String templates example (assuming a separate “authors” collection with “name” and “twitterHandle” fields with subfields “first” and “last” for the “name” field):
- label: "Post Author"
  name: "author"
  widget: "relation"
  collection: "authors"
  search_fields: ['name.first']
  value_field: "{{slug}}"
  display_fields: ["{{twitterHandle}} - {{followerCount}}"]

The generated UI input will search the authors collection by name, and display each author’s handle and follower count. On selection, the author entry slug is saved for the field.

  • Referencing a file collection list field example (assuming a separate “relation_files” collection with a file named “cities” with a list field “cities” with subfields “name” and “id”):
- label: "City"
  name: "city"
  widget: "relation"
  collection: "relation_files"
  file: "cities"
  search_fields: ["cities.*.name"]
  display_fields: ["cities.*.name"]
  value_field: "cities.*.id"

The generated UI input will search the cities file by city name, and display each city’s name. On selection, the city id is saved for the field.

Select

The select widget allows you to pick a string value from a dropdown menu.

  • Name: select
  • UI: select input
  • Data type: string or array
  • Options:
    • default: options must contain any default values
      • string values: accepts a string; defaults to an empty string. Accepts an array of strings and defaults to an empty array with multiple: true enabled.
      • object with label and value fields: accepts an object with label and value field or an array of such objects when multiple: true is enable. Defaults to no value
    • options: (required) there are two ways to list of options for the dropdown menu:
      • string values: the dropdown displays the value directly
      • object with label and value fields: the label displays in the dropdown; the value saves in the file
    • multiple: accepts a boolean; defaults to false
    • min: minimum number of items; ignored if multiple is false
    • max: maximum number of items; ignored if multiple is false
  • Example (options as strings):
- label: "Align Content"
  name: "align"
  widget: "select"
  options: ["left", "center", "right"]

Selecting the center option, will save the value as:

align: "center"
  • Example (options as objects):
- label: "City"
  name: "airport-code"
  widget: "select"
  options:
    - { label: "Chicago", value: "ORD" }
    - { label: "Paris", value: "CDG" }
    - { label: "Tokyo", value: "HND" }

Selecting the Chicago option, will save the value as:

airport-code: "ORD"
  • Example (multiple):
- label: "Tags"
  name: "tags"
  widget: "select"
  multiple: true
  options: ["Design", "UX", "Dev"]
  default: ["Design"]
  • Example (min/max):
- label: "Tags"
  name: "tags"
  widget: "select"
  multiple: true
  min: 1
  max: 3
  options: ["Design", "UX", "Dev"]
  default: ["Design"]

String

The string widget translates a basic text input to a string value. For larger textarea inputs, use the text widget.

  • Name: string
  • UI: text input
  • Data type: string
  • Options:
    • default: accepts a string; defaults to an empty string
  • Example:
    - {label: "Title", name: "title", widget: "string"}

Text

The text widget takes a multiline text field and saves it as a string. For shorter text inputs, use the string widget.

  • Name: text
  • UI: HTML textarea
  • Data type: string
  • Options:
    • default: accepts a string; defaults to an empty string
  • Example:
    - {label: "Description", name: "description", widget: "text"}

 

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