Drafts and timestamp-based publishing in Eleventy

Exercise more control over when, and if, your posts appear.

2022-12-21

The Eleventy static site generator (SSG) has many marvelous powers but, for those capabilities you want that it doesn’t (yet) sport, you often have to add them yourself, either through pre-cooked plugins or your own code. This post is about the latter.

In this case, it’s code that will give your Eleventy-based site two very nice features that come standard with some other SSGs: drafts and timestamp-based publishing. There already are quite a few articles out there (as noted at the end of this one) about adding one or the other of these two features, but I’ve yet to find one that mentions both and uses currently available Eleventy goodies, so this will be an attempt to remedy that.

Why you want them

First, let’s talk about these features and why you’d benefit from them, in case you’ve never previously encountered them. In short, each enables you to control when you want content to appear.

In drafts-supporting SSGs, you can use a content file’s front matter to identify the file (e.g., a post like this one) as being a draft. This tells the SSG that it shouldn’t publish it yet. Thus, you can have such a file in the online repo from which your site is built and not worry about it becoming part of your site’s live content.

Timestamp-based publishing is similar except that, in this case, it’s — you guessed it — the file’s timestamp (also usually in the front matter) which controls the content’s appearance or lack thereof on the website. A “future” post won’t appear if you build the site prior to the timestamp of that file. Mind you, this doesn’t mean the file’s content will magically appear on your website when its timestamp is no longer in the future; since we’re talking about static site builds here, the content will appear only with the first build past that timestamp.1

What advantages do you gain from having these features?

  • If you accidentally commit a draft post or “future post,” you don’t have to worry about its going live. [Bleep] Happens — so it’s nice to have a safety net.
  • You can enjoy the convenience of multi-device editing of content on a public repo without its getting published before you’re ready (perhaps using a workflow like the one I described in 2019’s “Roger, Copy,” a review of the superb Working Copy app for iOS and, now, iPadOS).2
  • Similarly, it makes multi-user editing easier, in case your site is a collaborative effort.

The code

Update, 2022-12-22: If you experience inconsistent behavior with the code provided below, try developing in Eleventy without the --incremental switch, which as of this writing can cause issues in some versions.

Update, 2023-01-24: The Eleventy site now has official documentation and code which you’ll likely find preferable to what’s contained here. Although the official suggestion is obstensibly about only the draft status aspect and not the timestamping, the documentation notes, “You might imagine how this could be extended to add a publishing date feature too: to exclude content from builds before a specific date set in a post’s front matter (or elsewhere in the data cascade).”

Eleventy’s computed data feature lets you (quoting the documentation) “inject Data properties into your data object that are based on other data values.”

For our purposes here, we’ll use eleventyComputed to determine each content file’s worthiness for publication based on the file’s draft status and timestamp. And, because we want Eleventy to make this happen through the project, we’ll put the code in the project’s global data directory, making its results available to all of the project’s templates. Typically, that directory is a top-level _data folder, so I’ll use that placement in the code example below. In order to work right, the file itself must be named eleventyComputed.js.3

eleventyComputed.js

// annotated contents of:
// _data/eleventyComputed.js

/*
	First, we decide whether to
	enable these features in
	**both** production and
	development modes or in
	**only** production. For this
	`bothModes` variable,
	`true` means both modes
	and `false` means prod-only.
	I've selected `true` because
	I like testing it locally.
*/
let bothModes = true

/*
	Now, we define the constant
	which checks whether a content
	file is a "future file," by
	comparing the file's `date`
	timestamp with the current
	date/time --- i.e., `Date.now()`.
*/
const isPageFromFuture = ({ date }) =>
	date.getTime() > Date.now()

/*
	Finally, we determine whether
	the file is OK to publish ---
	i.e., whether it gets assigned
	a permalink *and* can be part
	of any Eleventy collections
	(the latter matters for things
	like automated posts lists).
	The file must be *neither*
	a future file *nor* a draft file
	to be published.
	We wrap this test within a
	conditional which uses the
	`bothModes` selection.
*/
if (bothModes) {
	// prod and dev modes
	module.exports = {
		permalink: (data) => {
			const { permalink, page } = data
			if (isPageFromFuture(page) || data.draft) {
				return false
				// no permalink assigned
			}
			return permalink
		},
		eleventyExcludeFromCollections: (data) => {
			const { eleventyExcludeFromCollections, page } = data
			if (isPageFromFuture(page) || data.draft) {
				return true
				// excluded from collections
			}
			return eleventyExcludeFromCollections
		},
	}
} else {
	// prod only
	module.exports = {
		permalink: (data) => {
			const { permalink, page } = data
			if (process.env.ELEVENTY_ENV === "production" && (isPageFromFuture(page) || data.draft)) {
				return false
				// no permalink assigned
			}
			return permalink
		},
		eleventyExcludeFromCollections: (data) => {
			const { eleventyExcludeFromCollections, page } = data
			if (process.env.ELEVENTY_ENV === "production" && (isPageFromFuture(page) || data.draft)) {
				return true
				// excluded from collections
			}
			return eleventyExcludeFromCollections
		},
	}
}

References

I’ve ordered these sources by each item’s date of initial publication, starting with the oldest.


  1. Of course, if your repo is public, people will be able to find a draft and/or “future” post there if they know where to look; so, if that constitutes a problem for you, consider whether your repo should be private, instead. ↩︎

  2. This is particularly useful for Apple device users, like me, because it’s probably best not to mix Git and iCloud↩︎

  3. In case you’re already eleventyComputed-savvy and wonder why this code doesn’t include an eleventyComputed statement, the name choice precludes that; see Eleventy issue #1104↩︎

NEXT   

PREVIOUS