Extending Nikola

Extending Nikola

Version: 7.7.12
Author: Roberto Alsina <ralsina@netmanagers.com.ar>

Niko­la is ex­ten­si­ble. Al­most all its func­tion­al­i­ty is based on plu­g­in­s, and you can add your own or re­place the pro­vid­ed ones.

Plug­ins con­sist of a meta­da­ta file (with .plug­in ex­ten­sion) and a Python mod­ule (a .py file) or pack­age (a fold­er con­tain­ing a __init__.py file.

To use a plug­in in your site, you just have to put it in a plug­ins fold­er in your site.

Plug­ins come in var­i­ous flavours, aimed at ex­tend­ing dif­fer­ent as­pect­s of Niko­la.

Command Plugins

When you run niko­la --help you will see some­thing like this:

$ nikola help
Nikola is a tool to create static websites and blogs. For full documentation and more
information, please visit https://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 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 theme                manage themes
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 avail­able com­mands in your ver­sion of Niko­la. Each and ev­ery one of those is a plug­in. Let’s look at a typ­i­cal ex­am­ple:

First, the serve.­plug­in file:

Name = serve
Module = serve

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


If you want to pub­lish your plug­in on the Plug­in In­dex, read­ the docs for the In­dex (and the .plug­in file ex­am­ples and ex­pla­na­tion­s).

For your own plug­in, just change the val­ues in a sen­si­ble way. The Mod­ule will be used to find the match­ing Python mod­ule, in this case serve.py, from which this is the in­ter­est­ing 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 number (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 men­tioned above, a plug­in can have op­tion­s, which the us­er can see by do­ing niko­la help com­mand and can lat­er use, for ex­am­ple:

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

-p ARG, --port=ARG        Port number (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 com­mand­s? Well, any­thing you wan­t, re­al­ly. I have im­ple­ment­ed a sort of plan­et us­ing it. So, be cre­ative, and if you do some­thing in­ter­est­ing, let me know ;-)

TemplateSystem Plugins

Niko­la sup­ports Mako and Jin­ja2. If you pre­fer some oth­er tem­plat­ing sys­tem, then you will have to write a Tem­plateSys­tem plug­in. Here’s how they work. ­First, you have to cre­ate a .plug­in file. Here’s the one for the Mako plug­in:

Name = mako
Module = mako

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


If you want to pub­lish your plug­in on the Plug­in In­dex, read­ the docs for the In­dex (and the .plug­in file ex­am­ples and ex­pla­na­tion­s).

You will have to re­place “mako” with your tem­plate sys­tem’s name, and oth­er data in the ob­vi­ous ways.

The “Mod­ule” op­tion is the name of the mod­ule, which has to look some­thing like this, a stub for a hy­po­thet­i­cal sys­tem called “Tem­plater”:

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."""

You can see a re­al ex­am­ple in the Jin­ja plug­in

Task Plugins

If you want to do some­thing that de­pends on the da­ta in your site, you prob­a­bly want to do a Task plug­in, which will make it be part of the niko­la build com­mand. These are the cur­rent­ly avail­able tasks, al­l pro­vid­ed by plu­g­in­s:

$ nikola list
Scanning posts....done!

These have ac­cess to the site ob­ject which con­tains your time­line and y­our con­fig­u­ra­tion.

The crit­i­cal bit of Task plug­ins is their gen_­tasks method, which yields doit tasks.

The de­tails of how to han­dle de­pen­den­cies, etc., are a bit too much for this ­doc­u­men­t, so I’ll just leave you with an ex­am­ple, the copy­_as­sets task. ­First the task_­copy­_as­set­s.­plug­in file, which you should copy and ed­it in the log­i­cal ways:

Name = copy_assets
Module = task_copy_assets

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


If you want to pub­lish your plug­in on the Plug­in In­dex, read­ the docs for the In­dex (and the .plug­in file ex­am­ples and ex­pla­na­tion­s).

And the task_­copy­_as­set­s.py file, in its en­tire­ty:

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 plug­ins im­ple­ment markup lan­guages, they take sources for posts or pages and cre­ate HTML or oth­er out­put files. A good ex­am­ple is the mis­a­ka plug­in.

They must provide:

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

If the com­pil­er pro­duces some­thing oth­er than HTML files, it should al­so im­ple­ment ex­ten­sion which re­turns the pre­ferred ex­ten­sion for the out­put file.

These plug­ins can al­so be used to ex­tract meta­da­ta from a file. To do so, the ­plu­g­in may im­ple­ment read­_meta­da­ta that will re­turn a dict con­tain­ing the meta­da­ta con­tained in the file.

RestExtension Plugins

Im­ple­ment di­rec­tives for re­Struc­tured­Tex­t, see me­di­a.py for a sim­ple ex­am­ple.

If your out­put de­pends on a con­fig val­ue, you need to make your post record a de­pen­den­cy on a pseu­do-­path, like this:


Then, when­ev­er the OP­TION­NAME op­tion is changed in con­f.py, the file will be re­built.

If your di­rec­tive de­pends or may de­pend on the whole time­line (like the post-list di­rec­tive, where adding new posts to the site could make it stale), you should record a de­pen­den­cy on the pseu­do-­path ####­­MAG­IC####­­TIME­­LINE.

MarkdownExtension Plugins

Im­ple­ment Mark­down ex­ten­sion­s, see mdx_niko­la.py for a sim­ple ex­am­ple.

Note that Python mark­down ex­ten­sions are of­ten al­so avail­able as sep­a­rate ­pack­ages. This is on­ly meant to ship ex­ten­sions along with Niko­la.

SignalHandler Plugins

These plug­ins ex­tend the Sig­nal­Han­dler class and con­nect to one or more sig­nals via blink­er.

The eas­i­est way to do this is to reim­ple­ment set_site() and just con­nect to what­ev­er sig­nals you want there.

Cur­rent­ly Niko­la emits the fol­low­ing sig­nal­s:

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.
new_post / new_page
When a new post is created, using the nikola new_post/nikola new_page commands. The signal data contains the path of the file, and the metadata file (if there is one).
existing_post / existing_page
When a new post fails to be created due to a title conflict. Contains the same data as new_post.

When the niko­la de­ploy com­mand is run, and there is at least one new en­try/­post since last_de­ploy. The sig­nal da­ta is of the for­m:

 '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

When a post/­page is com­piled from its source to htm­l, be­fore any­thing else is done with it. The sig­nal ­da­ta is in the for­m:

 'source': # the path to the source file
 'dest': # the path to the cache file for the post/page
 'post': # the Post object for the post/page

One ex­am­ple is the de­ploy_hooks plug­in.

ConfigPlugin Plugins

Does noth­ing speci­fic, can be used to mod­i­fy the site ob­ject (and thus the con­fig).

Put all the mag­ic you want in set_site(), and don’t for­get to run the one from su­per(). Ex­am­ple plug­in: navs­to­ries

PostScanner Plugins

Get posts and sto­ries from “some­where” to be added to the time­line. The on­ly cur­rent­ly ex­ist­ing plug­in of this kind reads them from disk.

Plugin Index

There is a plug­in in­dex, which stores al­l of the plug­ins for Niko­la peo­ple want­ed to share with the world.

You may want to read the README for the In­dex if you want to pub­lish your pack­age there.

Template Hooks

Plug­ins can use a hook sys­tem for adding stuff in­to tem­plates. In or­der to use it, a plug­in must reg­is­ter it­self. The fol­low­ing hooks cur­rent­ly ex­ist:

  • ex­tra_­head (not equal to the con­fig op­tion!)
  • body_end (not equal to the con­fig op­tion!)
  • page_­head­er
  • menu
  • menu_alt (right-­side menu in boot­strap, af­ter menu in base)
  • page_­foot­er

For ex­am­ple, in or­der to reg­is­ter a script in­to ex­tra_­head:

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

There is al­so an­oth­er API avail­able. It al­lows use of dy­nam­i­cal­ly gen­er­at­ed HTM­L:

# 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 sec­ond ar­gu­ment to ap­pend() is used to de­ter­mine whether the func­tion needs ac­cess to the cur­rent tem­plate con­text and the site. If it is set to True, the func­tion will al­so re­ceive site and con­text key­word ar­gu­ments. Ex­am­ple 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'!')


Some (hope­ful­ly al­l) markup com­pil­ers sup­port short­codes in these form­s:

{{% foo %}}  # No arguments
    {{% foo bar %}}  # One argument, containing "bar"
    {{% foo bar baz=bat %}}  # Two arguments, one containing "bar", one called "baz" containing "bat"

    {{% foo %}}Some text{{% /foo %}}  # one argument called "data" containing "Some text"

So, if you are cre­at­ing a plug­in that gen­er­ates markup, it may be a good idea ­to reg­is­ter it as a short­code in ad­di­tion of to re­struc­tured text di­rec­tive or ­mark­down ex­ten­sion, thus mak­ing it avail­able to all markup for­mat­s.

To im­ple­ment your own short­codes from a plug­in, you can cre­ate a plug­in in­her­it­ing Short­code­Plu­g­in and cal­l Niko­la.reg­is­ter_short­code(­name, func) with the fol­low­ing ar­gu­ments:

Name of the shortcode (“foo” in the examples above)
A function that will handle the shortcode

The short­code han­dler must ac­cept the fol­low­ing named ar­gu­ments (or ­vari­able key­word ar­gu­ments):

An instance of the Nikola class, to access site state
If the shortcut is used as opening/closing tags, it will be the text between them, otherwise None.

If the short­code tag has ar­gu­ments of the form foo=bar they will be ­passed as named ar­gu­ments. Ev­ery­thing else will be passed as po­si­tion­al ar­gu­ments in the func­tion cal­l.

So, for ex­am­ple:

{{% foo bar baz=bat beep %}}Some text{{% /foo %}}

As­sum­ing you reg­is­tered foo_han­dler as the han­dler func­tion for the short­code named foo, this will re­sult in the fol­low­ing call when the above short­code is en­coun­tered:

foo_handler("bar", "beep", baz="bat", data="Some text", site=whatever)

Template-based Shortcodes

An­oth­er way to de­fine a new short­code is to add a tem­plate file to the short­codes di­rec­to­ry of your site. The tem­plate file must have the short­code name as the base­name and the ex­ten­sion .tm­pl. For ex­am­ple, if you want to add a new short­code named foo, cre­ate the tem­plate file as short­codes/­foo.tm­pl.

When the short­code is en­coun­tered, the match­ing tem­plate will be ren­dered with­ its con­text pro­vid­ed by the ar­gu­ments giv­en in the short­code. Key­word ar­gu­ments are passed di­rect­ly, i.e. the key be­comes the vari­able name in the tem­plate ­names­pace with a match­ing string val­ue. Non-key­word ar­gu­ments are passed as string val­ues in a tu­ple named _args. As for nor­mal short­codes with a han­dler func­tion, site and da­ta will be added to the key­word ar­gu­ments.


The fol­low­ing short­code:

{{% foo bar="baz" spam %}}

With a tem­plate in short­codes/­foo.tm­pl with this con­tent (us­ing Jin­ja2 syn­tax in this ex­am­ple):

<div class="{{ _args[0] if _args else 'ham' }}">{{ bar }}</div>

Will re­sult in this out­put:

<div class="spam">baz</div>

State and Cache

Some­times your plug­ins will need to cache things to speed up fur­ther ac­tion­s. Here are the con­ven­tions for that:

  • If it’s a file, put it some­where in `self.site.­­con­­fig['­­CACHE_­­FOLD­ER']` (de­faults to `cache/`.
  • If it’s a val­ue, use `self.site.­cache.set(key, val­ue)` to set it and `self.site.­cache.get(key)` to get it. The key should be a string, the val­ue should be json-en­cod­able (so, be care­ful with date­time ob­ject­s)

The val­ues and files you store there can and will be delet­ed some­times by the us­er. They should al­ways be things you can re­con­struct with­out los­sage. They are throw­aways.

On the oth­er hand, some­times you want to save some­thing that is not a throw­away. These are things that may change the out­put, so the us­er should not delete them. We call that state. To save state:

  • If it’s a file, put it some­where in the work­ing di­rec­to­ry. Try not to do that please.
  • If it’s a val­ue, use `self.site.s­tate.set(key, val­ue)` to set it and `self.s­tate.­cache.get(key)` to get it. The key should be a string, the val­ue should be json-en­cod­able (so, be care­ful with date­time ob­ject­s)

The `cache` and `s­tate` ob­jects are rather sim­plis­tic, and that’s in­ten­tion­al. They have no de­fault val­ues: if the key is not there, you will get `None` and like it. They are meant to be both thread­safe, but hey, who can guar­an­tee that sort of thing?

There are no sec­tion­s, and no ac­cess pro­tec­tion, so let’s not use it to store pass­words and such. Use re­spon­si­bly.