Private folder + Hook plugin

I store my private posts in a _private/_posts directory in the root of the Jekyll project and use a custom Jekyll Hook plugin stored at _plugins/private-posts.rb to decide if the posts should be included in the build or not. An environment variable named PRIVATE is passed into the serve command when you want the private posts to be displayed eg PRIVATE=true bundle exec jekyll serve.

# Private Posts
# -------------
# _plugins/private-posts.rb 
# Allows for posts stored within the `_private` directory to be published.
# Enable this feature by passing the `PRIVATE` environment variable in when
# serving eg `PRIVATE=true bundle exec jekyll serve`

Jekyll::Hooks.register :site, :pre_render do | site |
	if ENV["PRIVATE"] == "true"
		site.reader.retrieve_posts("#{site.source}/_private")
		Jekyll.logger.info "Private posts:", "enabled".red
	else
		Jekyll.logger.info "Private posts:", "disabled. Enable with PRIVATE=true"
	end
end

How it looks when you run the server

# Enable private posts
# (all posts in _private/_posts will be published)
roast@planetroast:~$ PRIVATE=true bundle exec jekyll serve

Configuration file: /home/code/planetroast.com/_config.yml
            Source: /home/code/planetroast.com
       Destination: /home/code/planetroast.com/_site
 Incremental build: disabled. Enable with --incremental
      Generating... 
     Private posts: enabled
                    done in 0.913 seconds.
 Auto-regeneration: enabled for '/home/code/planetroast.com'
    Server address: http://127.0.0.1:4000
  Server running... press ctrl-c to stop.


# Regular serve command
# (private posts will be hidden)
roast@planetroast:~$ bundle exec jekyll serve

Configuration file: /home/code/planetroast.com/_config.yml
            Source: /home/code/planetroast.com
       Destination: /home/code/planetroast.com/_site
 Incremental build: disabled. Enable with --incremental
      Generating... 
     Private posts: disabled. Enable with PRIVATE=true 
                    done in 0.844 seconds.
 Auto-regeneration: enabled for '/home/code/planetroast.com'
    Server address: http://127.0.0.1:4000
  Server running... press ctrl-c to stop.

You can have private drafts too

If you have any drafts which are private you can store them in _private/_drafts/ and they will work just the same as regular drafts:

# Only show 'regular' drafts stored in the main _drafts folder
bundle exec jekyll serve --draft

# Show all drafts including those in _private/_drafts
PRIVATE=true bundle exec jekyll serve --draft

Private posts in Jekyll using Collections

If you like to organise your Jekyll posts into collections and have set a custom collections_dir in your _config then the approach is different because the posts will be assigned to the ‘private’ collection instead of Jekyll’s built in ‘posts’ collection.

This means that the private posts won’t be rendered by default but we can easily fix that by modifying the site config via our plugin. It also means that the private posts won’t appear alongside your regular posts which we can fix by appending them to the site.posts.docs array.

# Private Posts
# -------------
# _plugins/private-posts.rb
# Add the private collection into the site config. We must set the `output`
# attribute otherwise the posts won't be rendered when the site builds. The
# permalink attribute is optional, by default it will be `/private/:slug`.
Jekyll::Hooks.register :site, :after_init do | site |
	if ENV["PRIVATE"] == "true"
		site.config['collections']['private'] = {
			"output" => true,
			"permalink" => "/:slug"
		}
		Jekyll.logger.info "Private posts:", "enabled".red
	else
		Jekyll.logger.info "Private posts:", "disabled. Enable with PRIVATE=true"
	end
end

# Add all the posts in the private collection into the main `_posts` collection.
# This step is necessary if you want your private posts to be treated as regular
# posts instead of posts within the private collection. 
Jekyll::Hooks.register :site, :pre_render do | site |
	if ENV["PRIVATE"] == "true"
		site.collections["private"].docs.each do | private_post |
			private_post["categories"] << "private" # Add category (optional)
			site.posts.docs << private_post
		end
	end
end

I’m not sure why we can’t use the site.reader.retreive_posts method in this case instead of manually adding each private to the site.posts array? The source code shows that it only reads posts and drafts from the custom_collections_dir, but as our _private directory is in the collections directory I would expect this to work. Not a huge deal in my case as I’d need to iterate through each private post anyway to add categories and front matter.

Rejected solutions

These are all potential solutions for making private posts in Jekyll which for one reason or another I rejected.

Drafts system

Jekyll has a built in system for managing drafts where drafts skip the build process and don’t appear on the site, so you could simply set all your private posts to drafts. The problem with this method is that in order to set a post as a draft you have to either omit the date or store it in the _drafts directory. Both of these are deal breakers for me because I want the dates on my posts and I certainly don’t want to mix up private posts with public posts.

Published: False

Why not use the built in published: false option in the front matter of your private posts? Then use the build command option --unpublished to show the private posts when you need them? Because it’s too easy to forget to set a post to unpublished and accidentally publish it, that’s why.

Front Matter

Setting a custom front matter of private: true on each private post is a possible option which could be combined with a custom pre render hook to prevent the private posts from being displayed, but it suffers from the same problem as above where it’s too easy to forget and accidentally publish a private post.

Collections

Update: Although I initially dismissed this solution as an unnecessary complication, since reorganising more of my data into collections and using a custom collections directory I’ve changed my opinion on this strategy and it’s now my preferred approach. The method I used is defined above.

Creating a custom collection for private posts might be an option as the publishing of the posts can be controlled via the output option in the _config file. But this is far from ideal because Jekyll won’t consider the files within the collection as posts (they’ll be private_posts or whatever you choose to name them) meaning you’ll have to merge them into the posts array or mess about with your templates to get them working. Plus you’ll need to edit the config each time you switch on private posts which is a pain and poses a risk of accidentally commiting the config with the private posts enabled.

Custom Command Gem

Jekyll 2.5 does allow for custom subcommands via plugins would would be great as it will be added to the existing list of Jekyll commands. However, I felt it was overkill to create and maintain a Gem just for this use case. Something told me that there was an simpler way to achieve private posts.