Eleventy: how to exclude draft collection items from build programmatically.

This will be a short one, mainly for my own future reference and for others moving to Eleventy and facing this issue. You’ll notice this is my third post on Eleventy in the past month, and I have no regrets. I enjoy using it.

Let’s get to it.

What are we trying to fix?

I have a bunch of blog posts in a directory named _posts, each of which has a YAML front-matter. If this set up sounds familiar to you, it’s because I moved from Jekyll.

Each blog post has a draft property, which takes a boolean value: true or false.

Whether you use a custom collection or the collections built by Eleventy using the tags front-matter — one thing it doesn’t do out of the box is skip building posts which have a draft value set to true.

This actually makes sense. It is not blog-aware like Jekyll is.

Unfortunately, this also means anyone can view such pages/posts even on a published site, if they are able to guess the permalink for a draft post or if you forget to set a meta tag for search engines to not index the draft pages or posts.

A straightforward solution is to just move draft posts to a _drafts directory, and add it to your .eleventyignore dot file.

If that works for you, great.

I use Forestry and not only did I want a more CMS-friendly solution, but also the ability to see all my draft posts on the development and staging environments, but not production.

There’s a clean solution to this. 🙂

Tell me more. What’s the fix?

Eleventy, in its 0.11.0 release, features an eleventyComputed property which can be set using a directory data file, among other options.

It allows you to dynamically set front-matter values at build-time. Eleventy computes them how you tell it to. Makes sense?

Creating a _posts.11tydata.js file under the _posts directory, we can control the front-matter data for all of the files in this directory.

We want to specify a front-matter of permalink: false where draft is true. When a collection item is not a draft, we want to return the same permalink value we’d have set in the layout files. In my case, I use Liquid, so the variables get parsed by Liquid.

// _posts.11tydata.js
module.exports = {
  eleventyComputed: {
    permalink: (data) => {
      let postPermalink =
        "/blog/{{ page.date | date: '%Y/%m/%d' }}/{{ slug }}/";

      if (process.env.ELEVENTY_ENV !== "production") return postPermalink;
      else return data.draft ? false : postPermalink;
    },
  },
};

Being able to change the permalink using computed properties is a special use-case. All other Eleventy properties cannot be overridden like this, although you can still set front matter for any of your own properties freely.

The data variable is passed on by Eleventy and includes the front-matter data for us to use, so we can use data.draft to check the draft status of each collection item, in this case, a blog post.


And there you have it. 🤩

You can use this for any other collection just as easily. If you’d like to control all items built by Eleventy, use a Global Data File.

Zach released the 0.11.0 version a couple of days ago. Go ahead, update, and use computed data if you want to! 👍


Update (2020-05-16): You could actually just return the permalink from the front-matter itself! This allows you to use a global data file without repeating your permalink structure from your templates. It would be like so:

// _posts.11tydata.js
module.exports = {
  eleventyComputed: {
    permalink: (data) => {
      if (process.env.ELEVENTY_ENV !== "production") return data.permalink;
      else return data.draft ? false : data.permalink;
    },
  },
};

I have implemented the same in my Smix Eleventy Starter.