Extending Nikola

Extending Nikola

Version: 7.3.1
Author: Roberto Alsina <[email protected]>

Nikola is extensible. Almost all its functionality is based on plugins, and you can add your own or replace the provided ones.

Plugins consist of a metadata file (with .plugin extension) and a Python module (a .py file) or package (a folder containing a __init__.py file.

To use a plugin in your site, you just have to put it in a plugins folder in your site.

Plugins come in various flavours, aimed at extending different aspects of Nikola.

Command Plugins

When you run nikola --help you will see something like this:

$ nikola help
Nikola is a tool to create static websites and blogs. For full documentation and more
information, please visit http://getnikola.com

Available commands:
nikola auto                 automatically detect site changes, rebuild
                            and optionally refresh a browser
nikola bootswatch_theme     given a swatch name from bootswatch.com and a
                            parent theme, creates a custom theme
nikola build                run tasks
nikola check                check links and files in the generated site
nikola clean                clean action / remove targets
nikola console              start an interactive python console with access to
                            your site and configuration
nikola deploy               deploy the site
nikola dumpdb               dump dependency DB
nikola forget               clear successful run status from internal DB
nikola help                 show help
nikola ignore               ignore task (skip) on subsequent runs
nikola import_blogger       import a blogger dump
nikola import_feed          import a RSS/Atom dump
nikola import_wordpress     import a WordPress dump
nikola init                 create a Nikola site in the specified folder
nikola install_theme        install theme into current site
nikola list                 list tasks from dodo file
nikola mincss               apply mincss to the generated site
nikola new_post             create a new blog post or site page
nikola run                  run tasks
nikola serve                start the test webserver
nikola strace               use strace to list file_deps and targets
nikola version              print the Nikola version number

nikola help                 show help / reference
nikola help <command>       show command usage
nikola help <task-name>     show task usage

That will give you a list of all available commands in your version of Nikola. Each and every one of those is a plugin. Let's look at a typical example:

First, the serve.plugin file:

Name = serve
Module = serve

Author = Roberto Alsina
Version = 0.1
Website = http://getnikola.com
Description = Start test server.


If you want to publish your plugin on the Plugin Index, read the docs for the Index (and the .plugin file examples and explanations).

For your own plugin, just change the values in a sensible way. The Module will be used to find the matching Python module, in this case serve.py, from which this is the interesting bit:

from nikola.plugin_categories import Command

# You have to inherit Command for this to be a
# command plugin:

class CommandServe(Command):
    """Start test server."""

    name = "serve"
    doc_usage = "[options]"
    doc_purpose = "start the test webserver"

    cmd_options = (
            'name': 'port',
            'short': 'p',
            'long': 'port',
            'default': 8000,
            'type': int,
            'help': 'Port nummber (default: 8000)',
            'name': 'address',
            'short': 'a',
            'long': '--address',
            'type': str,
            'default': '',
            'help': 'Address to bind (default:',

    def _execute(self, options, args):
        """Start test server."""
        out_dir = self.site.config['OUTPUT_FOLDER']
        if not os.path.isdir(out_dir):
            print("Error: Missing '{0}' folder?".format(out_dir))
            httpd = HTTPServer((options['address'], options['port']),
            sa = httpd.socket.getsockname()
            print("Serving HTTP on", sa[0], "port", sa[1], "...")

As mentioned above, a plugin can have options, which the user can see by doing nikola help command and can later use, for example:

$ nikola help serve
Purpose: start the test webserver
Usage:   nikola serve [options]

-p ARG, --port=ARG        Port nummber (default: 8000)
-a ARG, ----address=ARG   Address to bind (default:

$ nikola serve -p 9000
Serving HTTP on port 9000 ...

So, what can you do with commands? Well, anything you want, really. I have implemented a sort of planet using it. So, be creative, and if you do something interesting, let me know ;-)

TemplateSystem Plugins

Nikola supports Mako and Jinja2. If you prefer some other templating system, then you will have to write a TemplateSystem plugin. Here's how they work. First, you have to create a .plugin file. Here's the one for the Mako plugin:

Name = mako
Module = mako

Author = Roberto Alsina
Version = 0.1
Website = http://getnikola.com
Description = Support for Mako templates.


If you want to publish your plugin on the Plugin Index, read the docs for the Index (and the .plugin file examples and explanations).

You will have to replace "mako" with your template system's name, and other data in the obvious ways.

The "Module" option is the name of the module, which has to look something like this, a stub for a hypothetical system called "Templater":

from nikola.plugin_categories import TemplateSystem

# You have to inherit TemplateSystem

class TemplaterTemplates(TemplateSystem):
    """Wrapper for Templater templates."""

    # name has to match Name in the .plugin file
    name = "templater"

    # A list of directories where the templates will be
    # located. Most template systems have some sort of
    # template loading tool that can use this.
    def set_directories(self, directories, cache_folder):
        """Sets the list of folders where templates are located and cache."""

    # You *must* implement this, even if to return []
    # It should return a list of all the files that,
    # when changed, may affect the template's output.
    # usually this involves template inheritance and
    # inclusion.
    def template_deps(self, template_name):
        """Returns filenames which are dependencies for a template."""
        return []

    def render_template(self, template_name, output_name, context):
        """Renders template to a file using context.

        This must save the data to output_name *and* return it
        so that the caller may do additional processing.

    # The method that does the actual rendering.
    # template_name is the name of the template file,
    # context is a dictionary containing the data the template
    # uses for rendering.
    def render_template_to_string(self, template, context):
        """Renders template to a string using context. """

    def inject_directory(self, directory):
        """Injects the directory with the lowest priority in the
        template search mechanism."""

Task Plugins

If you want to do something that depends on the data in your site, you probably want to do a Task plugin, which will make it be part of the nikola build command. There are the currently available tasks, all provided by plugins: T .. sidebar:: Other Tasks

There are also LateTask plugins, which are executed later, and TaskMultiplier plugins that take a task and create more tasks out of it.
$ nikola list
Scanning posts....done!

These have access to the site object which contains your timeline and your configuration.

The critical bit of Task plugins is their gen_tasks method, which yields doit tasks.

The details of how to handle dependencies, etc., are a bit too much for this document, so I'll just leave you with an example, the copy_assets task. First the task_copy_assets.plugin file, which you should copy and edit in the logical ways:

Name = copy_assets
Module = task_copy_assets

Author = Roberto Alsina
Version = 0.1
Website = http://getnikola.com
Description = Copy theme assets into output.


If you want to publish your plugin on the Plugin Index, read the docs for the Index (and the .plugin file examples and explanations).

And the task_copy_assets.py file, in its entirety:

import os

from nikola.plugin_categories import Task
from nikola import utils

# Have to inherit Task to be a task plugin
class CopyAssets(Task):
    """Copy theme assets into output."""

    name = "copy_assets"

    # This yields the tasks
    def gen_tasks(self):
        """Create tasks to copy the assets of the whole theme chain.

        If a file is present on two themes, use the version
        from the "youngest" theme.

        # I put all the configurations and data the plugin uses
        # in a dictionary because utils.config_changed will
        # make it so that if these change, this task will be
        # marked out of date, and run again.

        kw = {
            "themes": self.site.THEMES,
            "output_folder": self.site.config['OUTPUT_FOLDER'],
            "filters": self.site.config['FILTERS'],

        tasks = {}
        for theme_name in kw['themes']:
            src = os.path.join(utils.get_theme_path(theme_name), 'assets')
            dst = os.path.join(kw['output_folder'], 'assets')
            for task in utils.copy_tree(src, dst):
                if task['name'] in tasks:
                tasks[task['name']] = task
                task['uptodate'] = task.get('uptodate', []) + \
                task['basename'] = self.name
                # If your task generates files, please do this.
                yield utils.apply_filters(task, kw['filters'])

PageCompiler Plugins

These plugins implement markup languages, they take sources for posts or pages and create HTML or other output files. A good example is the misaka plugin.

They must provide:

Function that builds a file.
Function that creates an empty file with some metadata in it.

If the compiler produces something other than HTML files, it should also implement extension which returns the preferred extension for the output file.

These plugins can also be used to extract metadata from file. To do so, the plugin may implement read_metadata that will return a dict containing the metadata contained in the file.

RestExtension Plugins

Implement directives for reStructuredText, see media.py for a simple example.

MarkdownExtension Plugins

Implement Markdown extensions, see mdx_nikola.py for a simple example.

SignalHandler Plugins

These plugins extend the SignalHandler class and connect to one or more signals via blinker.

The easiest way to do this is to reimplement set_site() and just connect to whatever signals you want there.

Currently Nikola emits the following signals:

Right after SignalHandler plugin activation.
When all tasks are loaded.
When all the configuration file is processed. Note that plugins are activated before this is emitted.
After posts are scanned.
When a new post is created, using the nikola new_post command. The signal data contains the path of the file, and the metadata file (if there is one).

When the nikola deploy command is run, and there is at least one new entry/post since last_deploy. The signal data is of the form:

 'last_deploy: # datetime object for the last deployed time,
 'new_deploy': # datetime object for the current deployed time,
 'clean': # whether there was a record of a last deployment,
 'deployed': # all files deployed after the last deploy,
 'undeployed': # all files not deployed since they are either future posts/drafts

ConfigPlugin Plugins

Does nothing specific, can be used to modify the site object (and thus the config).

Put all the magic you want in set_site(), and don’t forget to run the one from super().

Plugin Index

There is a plugin index, which stores all of the plugins for Nikola people wanted to share with the world.

You may want to read the README for the Index if you want to publish your package there.

Template Hooks

Plugins can use a hook system for adding stuff into templates. In order to use it, a plugin must register itself. The following hooks currently exist:

  • extra_head (not equal to the config option!)
  • body_end (not equal to the config option!)
  • page_header
  • menu
  • menu_alt (right-side menu in bootstrap, after menu in base)
  • page_footer

For example, in order to register a script into extra_head:

# In set_site
site.template_hooks['extra_head'].append('<script src="/assets/js/fancyplugin.js">')

There is also another API available. It allows use of dynamically generated HTML:

# In set_site
def generate_html_bit(name, ftype='js'):
    return '<script src="/assets/{t}/{n}.{t}">'.format(n=name, t=ftype)

site.template_hooks['extra_head'].append(generate_html_bit, False, 'fancyplugin', type='js')

The second argument to append() is used to determine whether the function needs access to the current template context and the site. If it it set to True, the function will also receive site and context keyword arguments. Example use:

# In set_site
def greeting(addr, endswith='', site=None, context=None):
    if context['lang'] == 'en':
        greet = u'Hello'
    elif context['lang'] == 'es':
        greet = u'¡Hola'

    t = u' BLOG_TITLE = {0}'.format(site.config['BLOG_TITLE'](context['lang']))

    return u'<h3>{greet} {addr}{endswith}</h3>'.format(greet=greet, addr=addr,
    endswith=endswith) + t

site.template_hooks['page_header'].append(greeting, True, u'Nikola Tesla', endswith=u'!')