Skip to content

Public Drafts With Jekyll

I’m using Jekyll, a static site generator, to build this website1. I always have a number of work-in-progress draft posts floating around, some being merely vague ideas, others close to being ready for publication.

Jekyll natively supports working with drafts: files you keep in your _drafts folder are not built by default. They are only included in the build when you explicitly tell Jekyll to do so, either by using the --drafts option when building the site or by specifying

show_drafts: true

in your _config.yml.

Publishing Drafts

Sometimes, I’d like to include one or more drafts in the published site to send the link to someone for review. Other blogging engines might refer to this as private posts. There are several ways to achieve this. I’ll outline three methods:

  1. Building drafts
  2. Hiding a regular post
  3. Using a custom collection

Building Drafts

You can enable building drafts either by specifying the --drafts option or setting show_drafts: true in your _config.yml. By default, this will include all your drafts in the listing of your blog posts, and that’s probably not what you want. Somewhere in your site you probably have a loop over all posts that lists them, something like this:

<ul>
{% for post in site.posts %}
  <li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>

You need to modify this loop to exclude drafts. This is straightforward: drafts in Jekyll already have a draft variable set to true when building drafts is enabled. All you need to do is add a simple check for it:

<ul>
{% for post in site.posts %}
  {% unless post.draft %}
  <li><a href="{{ post.url }}">{{ post.title }}</a></li>
  {% endunless %}
{% endfor %}
</ul>

If you have an RSS feed, you should exclude drafts there as well:

<!-- feed boilerplate -->
{% for post in site.posts %}
  {% unless post.draft %}
    <entry>
      <title>{{ post.title }}</title>
      <link href="{{ site.url }}{{ post.url }}"/>
      <updated>{{ post.date | date_to_xmlschema }}</updated>
      <id>{{ site.url }}{{ post.id }}</id>
      <content type="html">{{ post.content | xml_escape }}</content>
    </entry>
  {% endunless %}
{% endfor %}

Note that if you are using the jekyll-sitemap plugin you will want to exclude drafts from there as well. You can do this by setting sitemap: false in the front matter for each draft, but of course that’s a tedious manual procedure and prone to errors. There’s also a certain risk of forgetting to remove the variable when publishing the post. The last method I present here has a more robust solution to this problem.

Hiding Regular Posts

This is a relatively straightforward method if you only want to include specific posts that are ready for review. Instead of building drafts, you just treat the draft as a regular post and put it in your _posts folder. Then you simply add the draft front matter variable:

---
title: Your Draft Title
draft: true
---

In order to hide the post, you follow exactly the same procedure as above: exclude posts with the draft front matter variable from your post listings, feed, and sitemap.

The major advantage of this approach is that it is more fine-grained: Instead of building and publishing all drafts, you only include those you want to publish for review.

Custom Collection

This is probably the most advanced method, but it allows for greater flexibility. Basically, drafts are a collection, and you can customize how this particular collection is processed. Let’s start by defining the drafts collection in _config.yml:

collections:
  drafts:
    output: true
    permalink: /:collection/:name

Next, we want to make sure that the drafts collection uses the post layout by default and is automatically excluded from the sitemap:

defaults:
  - scope:
      path: "_drafts"
    values:
      layout: "post"
      sitemap: false

The good thing is that Jekyll still treats those posts as drafts for filtering purposes: they have the draft variable defined, so you can exclude them from post listings or feeds as shown above. You can even add a possibly hidden drafts.md page to only list your drafts:

---
layout: page
title: Drafts
description: "Draft articles"
sitemap: false
---

<ul>
{% for post in site.drafts %}
  <li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>

Voilà!

  1. I’m building my site using GitHub pages, so I’m stuck with Jekyll 3.X for now. All testing for this article was done using Jekyll 3.9.5. 

☞ Subscribe to the newsletter for updates on new blog posts.