Published 2023-02-12.
Last modified 2023-02-18.
Time to read: 3 minutes.
After writing several dozen Jekyll plugins, I distilled the common code into
jekyll_plugin_support
.
This Ruby gem facilitates writing and testing Jekyll plugins,
and handles the standard housekeeping that every Jekyll tag and block plugin requires.
Logging, parsing arguments, obtaining references to the
site
and page
objects, etc. are all handled.
The result is faster Jekyll plugin writing, with fewer bugs.
At present, only Jekyll tags and blocks are supported.
Plugins that were written using Jekyll Plugin Support include:
-
The
all_collections
tag fromjekyll_all_collections
-
Demo plugins from
jekyll_plugin_support
flexible_include
jekyll_href
jekyll_img
jekyll_plugin_template
jekyll_outline
jekyll_pre
jekyll_quote
Installation
Jekyll_plugin_support
is packaged as a Ruby gem.
If your custom plugin will reside in a Jekyll project’s _plugins
directory,
add the following line to your Jekyll plugin’s Gemfile
.
group :jekyll_plugins do gem 'jekyll_plugin_support' end
Otherwise, if your custom plugin will be packaged into a gem,
add the following to your plugin’s .gemspec
:
Gem::Specification.new do |spec| ... spec.add_dependency 'jekyll_plugin_support' ... end
Install the jekyll_plugin_support
gem in the usual manner:
$ bundle install
About
JekyllSupport::JekyllBlock
and JekyllSupport::JekyllTag
provide support for
Jekyll tag block plugins and
Jekyll tag plugins, respectively.
They are very similar in construction and usage.
Instead of subclassing your custom Jekyll block tag class from Liquid::Block
,
subclass from JekyllSupport::JekyllBlock
.
Similarly, instead of subclassing your custom Jekyll tag class from Liquid::Tag
,
subclass from JekyllSupport::JekyllTag
.
Both JekyllSupport
classes instantiate new instances of
PluginMetaLogger
(called @logger
) and
JekyllPluginHelper
(called @helper
).
JekyllPluginHelper
defines a generic initialize
method,
and your tag or block tag class should not need to override it.
Also, your tag or block tag class should not define a method called render
,
because jekyll_plugin_support
defines one.
Instead, define a method called render_impl
.
For tags, render_impl
does not accept any parameters.
For block tags, a single parameter is required,
which contains text passed from your block in the page.
Your implementation of render_impl
can parse parameters passed to the tag / block tag, as described in
Tag Parameter Parsing.
The following variables are predefined within render
.
See the Jekyll documentation for more information.
@argument_string
– Original unparsed string from the tag in the web page@config
– Jekyll configuration data@envs
– Jekyll environment variables, as shown in the screenshot below@layout
– Front matter specified in layouts-
@mode
– possible values are:development
,production
, ortest
@page
– Jekyllpage
variable-
@paginator
– Only has a value when a paginator is active; they are only available in index files. @site
– Jekyllsite
variable@tag_name
– Name of the tag or block plugin@theme
– Theme variables (introduced in Jekyll 4.3.0)

Usage
The following minimal examples define VERSION
,
which is important because JekyllPluginHelper.register
logs that value when registering the plugin.
This is how you would define plugins in the _plugins
directory:
require 'jekyll_plugin_support' module Jekyll class DemoTag < JekyllSupport::JekyllTag VERSION = '0.1.0'.freeze def render_impl # Your Jekyll plugin logic goes here end JekyllPluginHelper.register(self, 'demo_tag') end end
require 'jekyll_plugin_support' module Jekyll class DemoBlock < JekyllSupport::JekyllBlock VERSION = '0.1.0'.freeze def render_impl(text) # Your Jekyll plugin logic goes here end JekyllPluginHelper.register(self, 'demo_block') end end
If your plugin is packaged as a gem, then you might need to include version.rb
into the plugin class.
For example, if your version module looks like this:
module MyPluginVersion VERSION = '0.5.0'.freeze end
Then your plugin can incorporate the VERSION
constant into your plugin like this:
require 'jekyll_plugin_support' require_relative 'my_plugin/version' module Jekyll class MyBlock < JekyllSupport::JekyllBlock include MyPluginVersion def render_impl(text) # Your code here end JekyllPluginHelper.register(self, 'demo_block') end end
No_arg_parsing Optimization
If your tag or block plugin only needs access to the raw arguments passed from the web page,
without tokenization, and you expect that the plugin might be invoked with large amounts of text,
derive your plugin from JekyllBlockNoArgParsing
or JekyllTagNoArgParsing
.
See the demo plugins for an example.
This feature is used by the
select
tag
in the jekyll_pre
plugin.
Demonstration Tag and Block Plugins
The jekyll_plugin_support
GitHub project includes a
demo
website.
Three short plugins are demonstrated,
and they show how to access the variables that the Jekyll Plugins Support gem provides.
The following examples use Ruby’s
squiggly heredoc operator (<<~
).
The squiggly heredoc operator removes the outmost indentation.
This provides easy-to-read multiline text literals.
require 'jekyll_plugin_support' module Jekyll class DemoTag < JekyllSupport::JekyllTag VERSION = '0.1.2'.freeze def render_impl @keyword1 = @helper.parameter_specified? 'keyword1' @keyword2 = @helper.parameter_specified? 'keyword2' @name1 = @helper.parameter_specified? 'name1' @name2 = @helper.parameter_specified? 'name2' <<~END_OUTPUT <pre>@helper.tag_name=#{@helper.tag_name} @mode=#{@mode} # Passed into Liquid::Tag.initialize @argument_string="#{@argument_string}" @helper.argv= #{@helper.argv.join("\n ")} # Liquid variable name/value pairs @helper.params= #{@helper.params.join(', ')} @helper.keys_values= #{(@helper.keys_values.map { |k, v| " #{k}=#{v}\n" }).join(" \n")} remaining_markup='#{@helper.remaining_markup}' @config['url']='#{@config['url']}' @site=#{@site} @page['description']=#{@page['description']} @page['path']=#{@page['path']} @keyword1=#{@keyword1} @keyword2=#{@keyword2} @name1=#{@name1} @name2=#{@name2} @envs=#{@envs.keys.join(' ')}</pre> END_OUTPUT end JekyllPluginHelper.register(self, 'demo_tag') end end
require 'jekyll_plugin_support' module Jekyll class DemoBlock < JekyllSupport::JekyllBlock VERSION = '0.1.1'.freeze def render_impl(text) @keyword1 = @helper.parameter_specified? 'keyword1' @keyword2 = @helper.parameter_specified? 'keyword2' @name1 = @helper.parameter_specified? 'name1' @name2 = @helper.parameter_specified? 'name2' <<~END_OUTPUT <pre>@helper.tag_name=#{@helper.tag_name} @mode=#{@mode} # Passed into Liquid::Block.initialize @argument_string="#{@argument_string}" @helper.argv= #{@helper.argv.join("\n ")} # Liquid variable name/value pairs @helper.params= #{@helper.params.join(', ')} @helper.remaining_markup='#{@helper.remaining_markup}' @helper.keys_values= #{(@helper.keys_values.map { |k, v| " #{k}=#{v}\n" }).join(" \n")} @config['url']='#{@config['url']}' @site.url=# {@site.url} @page['description']=#{@page['description']} @page['path']=#{@page['path']} @keyword1=#{@keyword1} @keyword2=#{@keyword2} @name1=#{@name1} @name2=#{@name2} text='#{text}' @envs=#{@envs.keys.join(' ')}</pre> END_OUTPUT end JekyllPluginHelper.register(self, 'demo_block') end end
The following is an example of no_arg_parsing
optimization.
require 'jekyll_plugin_support'
module Jekyll
class DemoTagNoArgs < JekyllSupport::JekyllTagNoArgParsing
VERSION = '0.1.0'.freeze
def render_impl
<<~END_OUTPUT
The raw arguments passed to this DemoTagNoArgs instance are:
'#{@argument_string}'
END_OUTPUT
end
JekyllPluginHelper.register(self, 'demo_tag_no_arg')
end
end