WritingsThoughts on things

September 10, 2019 4 min read

Drafts for Gatsby

Draft functionality is common place in content management systems but implementing them in simple Gatsby sites always seemed a bit hacky. Now, using Gatsby's Schema Customization API, implementing drafts functionality for content is painless and straightforward.

Number 2 PencilsNumber 2 Pencils
Number 2 Pencils

Since creating my Gatsby site, I've wanted to add drafts functionality for my blog posts and while possible, the solutions all seemed less than ideal. Luckily, as of Gatsby 2.2.0 that all changed with the new Schema Customization API. Let's take a look at how easy it is to add drafts functionality to our markdown content.

gatsby-node.js changes

Initially, I thought it would make sense to add a new draft field in the frontmatter of all of my markdown files but if you have a large site already that can be a pain. Instead, there is a better way!. We can set a default value for a draft field so that we will only need to add draft: true to any markdown files we want to designate as drafts.

exports.createSchemaCustomization = ({ actions, schema }) => {
  const { createTypes, createFieldExtension } = actions

  createFieldExtension({
    name: `defaultFalse`,
    extend() {
      return {
        resolve(source, args, context, info) {
          if (source[info.fieldName] == null) {
            return false
          }
          return source[info.fieldName]
        },
      }
    },
  })

  createTypes(`
    type MarkdownRemark implements Node {
      frontmatter: Frontmatter
    }
    type Frontmatter {
      draft: Boolean @defaultFalse
    }
  `)
}

Let's break down the code above by understanding the createTypes function.

createTypes can be used to define, fix, or extend a Gatsby GraphQL type that represents some of your app’s data. Think of it like an escape hatch to politely inform Gatsby of your data’s shape.

In addition to using createTypes, we'll define a nested type on the MarkdownRemark type so we can define our new draft field to be of type boolean. With out type set, we'll now create a directive which we will call @defaultFalse. This directive comes from our createFieldExtension action which allows us to define the default draft status for our content. In our case, we want to default the draft status of our content to false, hence the name of our directive defaultFalse. After setting the name of our field extension, we then extend our MarkdownRemark implementation to check each frontmatter field within our markdown files and return it's value.

In our resolve, the source variable contains each of our frontmatter fields with info.fieldName representing the name of the field. Any fields that are not provided in the frontmatter will have a default value of null. Since our draft field will only be present when we want to designate a post as a draft, we return false if the draft field is not present. For all of our other fields, we will simply return the value of each field.

Setting Draft Status

Now that we have our draft functionality defined to work in cases where the field is provided and even when it is not, let's put it to use.

---
title: Working Title
date: 2019-09-10T14:09:32-04:00
draft: true
---

By only adding draft: true in our frontmatter and updating our GraphQL queries to use the drafts field, we can filter out all content with draft: true set in the frontmatter. Our GraphQL query might look something like:

query {
  allMarkdownRemark(filter: { frontmatter: { draft: { eq: false } } }) {
    nodes {
      frontmatter {
        title
        date
      }
    }
  }
}

Our query will now only return markdown content which does not have a field called draft set to true.

If you're like me, writing blog posts can be a slow process and having the ability to use drafts is vital for my workflow without compromising the ability to continuously deploy the site for other content updates. It also allows me to use git as a history for my blog post revisions before they are set to be published to my site.


GatsbyJS