A CMS for static site generators. Give non-technical users a simple way to edit and add content to any site built with a static site generator.
Try the UI demo here: cms.netlify.com.
Netlify CMS is a single-page app that you pull into the /admin
part of your site.
It presents a clean UI for editing content stored in a Git repository.
You setup a YAML config to describe the content model of your site, and typically tweak the main layout of the CMS a bit to fit your own site.
When a user navigates to /admin
she'll be prompted to login, and once authenticated
she'll be able to create new content or edit existing content.
The easiest way to get started playing around with netlify CMS, is to clone one of our starter templates and start hacking:
Netlify CMS is an Ember app. To install it in your site, add an /admin
folder in
your source directory and use this index.html
as a template:
<!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 stylesheets from your site here -->
<link rel="stylesheet" href="//cms.netlify.com/assets/cms.css" />
<!-- Include a CMS specific stylesheet here -->
<base href="/admin/">
</head>
<body>
<script src="//cms.netlify.com/assets/vendor.js"></script>
<script src="//cms.netlify.com/assets/cms.js"></script>
</body>
</html>
Add a config.yml
file to the /admin
folder and configure your content model:
backend:
name: github-api
repo: owner/repo # Path to your Github repository
branch: master # Branch to update (master by default)
media_folder: "img/uploads" # Folder where user uploaded files should go
collections: # A list of collections the CMS should be able to edit
- name: "post" # Used in routes, ie.: /admin/collections/:slug/edit
label: "Post" # Used in the UI, ie.: "New Post"
folder: "_posts" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
fields: # The fields each document in this collection have
- {label: "Title", name: "title", widget: "string", tagname: "h1"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Foo", name: "foo", widget: "foo"}
meta: # Meta data fields. Just like fields, but without any preview element
- {label: "Publish Date", name: "date", widget: "datetime"}
- name: "settings"
label: "Settings"
files:
- name: "general"
label: "General settings"
file: "_settings/general.json"
fields:
- {label: "Main site title", name: "site_title", widget: "string"}
- {label: "Number of fronpage posts", name: "post_count", widget: "number"}
- {label: "Site cover image", name: "cover", widget: "image"}
Netlify CMS works with the concept of collections of documents that a user can edit.
Collections basically comes in three forms:
- A
folder
. Set thefolder
attribute on the collection. Each document will be a file in this folder. Each document will have the same format, fields and meta fields. - A list of
files
. Set thefiles
attribute on the collection. You can set fields that all files in the folder shares directly on the collection, and set specific fields for each file. This is great when you have files with a different structure. - A
file
. Warning, not implemented yet. This is a collection stored in a single file. Typically a YAML file or a CSV with an array of items.
Each collection has a list of fields (or files with their individual fields). Each field has a label
, a name
and a widget
.
Setting up the right collections is the main part of integrating netlify CMS with your site. It's where you decide exactly what content editors can work with, and what widgets should be used to edit each field of your various files or content types.
Often it's useful to have a few different environments defined in your config. By default the config will be loaded as is, but if you set a global CMS_ENV variable in a script tag in your admin/index.html, any attributes in that environment will take precedence over the default attributes.
Example:
backend:
name: netlify-api
url: http://localhost:8080
production:
backend: github-api
repo: netlify/netlify-home
branch: master
# rest of the config here...
Now when working locally, the CMS will use a local instance of the netlify git API, but if you make sure to set window.CMS_ENV="production"
in your production builds, then the CMS will work on Github's API in production.
Some Static Site Generators (looking at you Hexo) won't copy a config.yml from
the admin folder into the build when generating a site. As an alternative you can
embed the config.yml directly in the admin/index.html
file like this:
<script type="application/x-yaml" id="cms-yaml-config">
# Config YAML goes here...
</script>
The default Github-based authenticator integrates with Netlify's Authentication Provider feature and the repository backend integrates directly with Github's API.
To get everything hooked up, setup continuous deployment from Github to Netlify and then follow the documentation to setup Github as an authentication provider.
That's it, now you should be able to go to the /admin
section of your site and
log in.
If you don't have a Github repo or just wan't to work locally, netlify CMS also has a local version of the Github api that you can run from any repo on your machine.
Grab it from the netlify-git-api repo, follow the installation instructions, then CD into the repo with your site and run:
netlify-git-api users add
netlify-git-api serve
This will add a new user and start serving an API for your repo.
Configure the backend like this in your config.yml
:
backend:
name: netlify-api
url: http://localhost:8080
Most static file generators, except from Jekyll, don't keep the files that'll be copied into the build folder when generating in their root folder.
This can create a problem for image and file paths when uploaded through the CMS.
Use the public_folder
setting in config.yml
to tell the CMS where the public
folder is located in the sources. A typical Middleman setup would look like this:
media_folder: "source/uploads" # Media files will be stored in the repo under source/uploads
public_folder: "source" # CMS now knows 'source' is the public folder and will strip this from the path
Actual content editing happens with a side by side view where each widget
has
a control for editing and a preview to give the content editor an idea of how the
content will look in the context of the published site.
Currently these widgets are built-in:
- string A basic text input
- markdown A markdown editor
- checkbox A simple checkbox
- date A date input
- datetime A date and time input
- number A number
- hidden Useful for setting a default value with a hidden field
- image An uploaded image
- object An object with it's own set of fields
- list A list of objects, takes it's own array of fields describing the individual objects
You can customize how entries in a collection are previewed easily by adding a handlebars template in your /admin/index.html
:
<script type="text/x-handlebars" data-template-name='cms/preview/my-collection'>
<h1>{{cms-preview field='title'}}</h1>
<div class="photo">{{cms-preview field='image'}}</div>
<div class="body">{{cms-preview field='body'}}</div>
</script>
The name of the template should be prefixed 'cms/preview/' and have the same name as the collection you want to preview.
User the {{cms-preview field='fieldname'}}
to insert the preview widget for a field.
You can use {{entry.fieldname}}
to access the actual value of a field in the template.
For widgets like markdown fields or images, you'll typically always want to use the {{cms-preview field='body'}} format, since otherwise you'll get the raw value of the field, rather than the HTML value.
The list widget is very powerful and allow you to have a list of structured object within an entry, that can be reordered via drag and drop.
Here's a basic example of an entry in a file based collection that lets the user edit a list of authors.
config.yml
collections:
- name: "settings"
label: "Settings"
- name: "authors"
label: "Authors"
file: "_data/authors.yml"
description: "Author descriptions"
fields:
- name: authors
label: Authors
widget: list
fields:
- {label: "Name", name: "name", widget: "string"}
- {label: "Description", name: "description", widget: "markdown"}
This will let the user edit a list of authors that each have a Name and a Description.
When configuring a custom preview for this entry, it's important to treat the list field a bit different than normal values on the entry, to make sure you can get the right output from {{cms-preview}}
within the list:
<script type="text/x-handlebars" data-template-name='cms/preview/authors'>
{{#cms-preview field="authors" as |author|}}
<div class="author">
<h2>{{author.name}}</h2>
<div class="description">{{cms-preview field="description" from=author}}</div>
</div>
{{/cms-preview}}
</script>
Note that for the list widget, we're using the cms-preview
tag in a new way, to iterate over
each item in the list, and when using cms-preview
for a specific item, we're setting a from
attribute to make sure we're showing the preview of the description
for that specific author, and not a global description
field from the entry itself.
The same rule applies when using a single object widget to group various fields together. You'll need this when editing data with nested keys like this:
{"posts": {"frontpage_limit": 5, "author": "Jon Doe"}}
As a field in a configuration file, this would look like:
- label: "Post settings"
name: posts
widget: object
fields:
- {label: "Number of posts on front page", name: "frontpage_limit", widget: "number"}
- {label: "Default Author", name: "author", widget: "string"}
And to setup a custom preview template for this, you would again use the "cms-preview" component:
{{#cms-preview field="posts" as |posts|}}
<p>Number on frontpage: {{posts.frontpage_limit}}</p>
<p>Default Author: {{posts.author}}</p>
{{/cms-preview}}
Again we're using the cms-preview to get us access to the inner object. You could use {{entry.posts.frontpage_limit}}
as well in simple cases, but once you have nested values that needs
more complex preview rendering (such as image or markdown widgets), you'll want to use this technique
to make sure you get the right kind of object in scope.
Some static site generators, like Jekyll or Hexo, will try to parse the handlebar tags in your templates. Both of these use the following syntax to use raw HTML:
{% raw %}
<script type="text/x-handlebars" data-template-name='cms/preview/my-collection'>
<h1>{{cms-preview field='title'}}</h1>
<div class="photo">{{cms-preview field='image'}}</div>
<div class="body">{{cms-preview field='body'}}</div>
</script>
{% endraw %}
It's easy to add a custom template for either the preview or the control part of
a widget by adding handlebars templates in your /admin/index.html
.
Each widget consists of two Ember components, a widget control and a widget preview.
You can overwrite the template for any widget control or preview like this:
<script type="text/x-handlebars" data-template-name='components/widgets/string-control'>
<div class="form-group">
<label>{{widget.label}}</label>
{{input value=raw_value class="form-control"}}
</div>
</script>
A typical handlebar template. Within the template you can access the actual
widget
object that exposes the label
and the value
of the field.
You can also access the field
property on the widget to access the raw field
object from the config.yml
.
It also exposes an entry
object that represents the full document that's being edited.
This can be useful if you need to combine various values in a preview template.
Here's a more advanced example of creating a new widget called author
just by adding
custom templates:
<script type="text/x-handlebars" data-template-name='components/widgets/author-control'>
<div class="form-group">
<label>{{widget.label}}</label>
{{view "select" content=widget.field.authors value=widget.value}}
</div>
</script>
<script type="text/x-handlebars" data-template-name='components/widgets/author-preview'>
Written by
<span class="author">{{widget.value}}</span>
on
<span class="date">{{time-format widget.entry.cmsDate 'LL'}}</span>
</script>
It can now be used in the config.yml
like this:
collections: # A list of collections the CMS should be able to edit
- name: "post" # Used in routes, ie.: /admin/collections/:slug/edit
label: "Post" # Used in the UI, ie.: "New Post"
folder: "_posts" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
fields: # The fields each document in this collection have
- {label: "Title", name: "title", widget: "string", tagname: "h1"}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Author", name: "author", widget: "author", authors: ["Matt", "Chris"]}
Here we use Ember's Select View
to let the user pick one of two authors and we use a custom preview to show the
output like: Written by Matt on April 29, 2015
.
Netlify CMS includes a time format helper so you can easily format dates with the {{time-format}}
helper via moment.js's formatting shortcuts.
{{time-format entry.date ""}}
When writing preview templates or widget templates, you can use any component or helper normally available in Ember apps, but apart from that netlify CMS adds a few extra helpers:
Netlify CMS has a built-in time-format helper for formatting date and time with the formatting syntax from moment.js.
<h2>Date: {{ time-format entry.date "dddd, MMMM Do YYYY, h:mm:ss a"}}</h2>
This would output something like:
<h2> Date: Friday, August 14th 2015, 2:12:34 pm</h2>
Netlify CMS can easily be extended with custom widgets.
Each widget consists of two Ember Components.
- Widget Control Component Used on the left site of the CMS as the input for the field value.
- Widget Preview Component Not always necesarry (will always fall back to a simple string preview), but for advanced widgets like the markdown editor, etc, a preview widget is needed to format the output of the field right.
As a small example, we'll look at how creating a "color" widget that'll let us define a field like this:
{"label": "color", "name": "color", "widget": "color"}
And then get a nice color picker.
The bare minimum for creating your own widget, is to create a widget template. Lets add this to our admin/index.html
:
<script type="text/x-handlebars" data-template-name='components/widgets/color-control'>
{{input value=widget.value type="color"}}
</script>
That's actually all it take for a really simple widget. Now any browser that implements <input type="color">
will show a color picker and update the preview in realtime with the value as we make changes.
Widget components have access to a widget
object. For all the details on this object, read the source code documentation.
Most of the time, you'll need to also add some custom behavior to your component that can't be handle by a handlebars template alone.
Lets try to write a color picker widget based on the jQuery spectrum color-picker instead of the browser's native color field.
First we make sure to include the assets needed for spectrum.
<script src="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.7.1/spectrum.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/spectrum/1.7.1/spectrum.min.css"></link>
Now that we have spectrum loaded, we need to make sure we initialize the plugin when our component is added to the DOM and cleanup when it's removed.
We'll start by changing our component template a bit:
<script type="text/x-handlebars" data-template-name='components/widgets/color-control'>
<input type='text' class="cms-color-control"/>
</script>
Instead of the built-in Ember input component, we'll just output a normal input tag that we can use as the element we attach spectrum to.
To add the logic for our control component, we call CMS.WidgetControl(componentName, implementation)
. For those familiar with Ember, this is the same as creating a new Ember component via Ember.Component.extend(implementation)
.
Here's the full Spectrum color picker control component (make sure to add this after the script tag that includes the CMS JavaScript):
<script>
// Create the Control component fro the "color" widget
CMS.WidgetControl("color", {
// We want this component to wrap it's template in a "div" so we can use
// this.$(...) to access the template DOM
tagName: "div",
// willInsertElement is a standard Ember callback that gets invoked when
// our component is inserted into the DOM
willInsertElement: function() {
var widget = this.get("widget");
// Setup spectrum with an initial value from the widget, and make sure
// we update the value when the user picks a color
this.$("input").spectrum({
color: this.get("widget.value"),
change: function(color) {
widget.set("value", color.toHexString());
}
});
},
// willDestroyElement is a standard Ember callback that gets invoked when
// our component is about to be removed from the DOM
willDestroyElement: function() {
this.$("input").spectrum("destroy");
}
});
</script>
That's it - now we'll get a nice spectrum color picker.
By default the default preview for this widget will simply output a string with the color value.
The simplest way to customize the preview, is to just create a preview template:
<script type="text/x-handlebars" data-template-name='components/widgets/color-preview'>
<div class="cms-color-preview" style="background: {{widget.value}};">{{widget.value}}</div>
</script>
Just like with the control part of our widget, we can also add some logic to our preview component.
In some cases it might be impossible to actually read the widget.value if the color of the text and the background gets too close. Lets add a bit of logic to always use the inverted color of the current value to show the color string.
First we update our template a bit:
<script type="text/x-handlebars" data-template-name='components/widgets/color-preview'>
<div class="cms-color-preview" style="background: {{widget.value}};">
<p style="color: {{invertedColor}}">{{widget.value}}</p>
</div>
</script>
Now we create a preview component for our color widget:
<script>
CMS.WidgetPreview("color", {
invertedColor: function() {
var color = this.get("widget.value") || "#fff";
color = color.substring(1); // remove #
color = parseInt(color, 16); // convert to integer
color = 0xFFFFFF ^ color; // invert three bytes
color = color.toString(16); // convert to hex
color = ("000000" + color).slice(-6); // pad with leading zeros
color = "#" + color; // prepend #
return color;
}.property("widget.value")
});
</script>
Just as with CMS.WidgetControl, this is the same as defining a new Ember Component via Ember.Component.extend(implementation)
. An important part of Ember is computed properties. In this case we define a function invertedColor
and use .property("widget.value")
to make this a computed property that depends on widget.value
. This way it'll get recomputed every time widget.value
changes, and we can use invertedColor
in our handlebars template.
Docs on file formats, internal APIs etc...
This is obviously still early days for Netlify CMS, there's a long list of features and improvements on the roadmap.