Change your colour scheme

Building a CMS for Eleventy


Three days ago, I tweeted this:


I said I wouldn’t be writing a CMS for Eleventy. It wasn’t going to happen, there’s no way. I’m not in the business of reinventing the wheel.

Anyway, here’s how I built a (very simple) CMS for an Eleventy site.


I wanted to build a proof-of-concept for something I’d had in mind a while ago, which was a little application that could build a static web page for a local café, and allow the owners to put together new menus and have them update without any intervention from a developer.

I knew it wasn’t hard to use external data sources with Eleventy - this site uses one to get book information for my reading list. What I wanted to do was seamlessly trigger that build and data retrieval.

Firstly I considered a different approach: committing files to a Git repository and pushing them. That’s fine in theory, but it’s very config-heavy, and relies on having an authenticated Github account attached, which isn’t ideal. I want to be able to trigger the actual build.


At its core, this is just an Express server with an SQLite database and the Eleventy programmatic API. I went with Express because it meant I could keep everything inside Javascript (well, Typescript), meaning I wouldn’t have to execute commands from whatever platform I’d written - simply, it makes it slightly easier from a package management perspective.

The flow is actually really simple. Once a user saves a menu, we trigger the Eleventy build in a separate directory. The directory contains a full Eleventy instance; this doesn’t rely on the end-user’s configuration, as the API means I can inject what config I need and leave everything else untouched. This then builds it separately, and I can serve the files any way I want.

Issues encountered

The Eleventy Programmatic API isn’t particularly well-documented, so I had to go digging through the code to work out what was going on in some spots. In particular, I’d assumed that the paths I provided for output directories and config files were relative to the input path, but that proved to be false - they’re actually relative to the working directory. So while I thought I was looking for .eleventy.js in /eleventy_dir/, it was actually looking in the directory of the Express app.

This was also true for passthrough copies, which proved to be a slight issue - one of the things I didn’t want to do was dictate how the Eleventy site should be configured. In the end, I found a “workaround” (read: horrible hack) that let me override the eleventyConfig.addPassthroughCopy function, and make relative paths absolute. Here’s the code for it below:

new Eleventy(
config: (eleventyConfig) => {
let addPassthrough = eleventyConfig.addPassthroughCopy.bind(eleventyConfig);
eleventyConfig.addPassthroughCopy = (file) => {
if (typeof file === "string") {
const filePath = {
[path.join(this._config.rootDir || "", file)]: file
return addPassthrough(filePath);
return addPassthrough(file);

eleventyConfig.addGlobalData("menus", () => {
return menus as CollectionItem[];

return {};

Like I said, a “workaround”.

Final thoughts

So this was a fun little experiment. It’s very rough-and-ready and doesn’t really do a lot, but it was good to spike out how that might be done. Eagle-eyed observers of the codebase we’ll see that there’s lots of boilerplate/half-finished code for other things I was working on. I’m planning on adding more features to the server, and then hopefully building an MVP of the menu application.

I think there are a few use cases for this, but mostly it’s a good way to build content-managed websites that are updated relatively-infrequently. I think the thing that I like about it is that it is very unprescriptive. Your specific Eleventy configuration isn’t important - it adds the data it needs, and then leaves it alone (well, everything except those file paths).

The source for the Express server can be found on my Github.

About the author

My face

I'm Lewis Dale, a software engineer and web developer based in the UK. I write about writing software, silly projects, and cycling. A lot of cycling. Too much, maybe.