Extending Nikola

Extending Nikola

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

Ni­ko­la is ex­ten­sible. Al­most all its func­tio­na­li­ty is ba­sed on plu­gins, and you can add your own or re­place the pro­vi­ded ones.

Plu­gins consist of a me­ta­da­ta file (with .plu­gin ex­ten­sion) and a Py­thon mo­dule (a .py fi­le) or pa­ckage (a fol­der contai­ning a __i­nit__.­py file.

To use a plu­gin in your si­te, you just have to put it in a plu­gins fol­der in your site.

Plu­gins come in va­rious fla­vours, ai­med at ex­ten­ding dif­ferent as­pects of Ni­ko­la.

Command Plugins

When you run ni­ko­la --­­help you will see so­me­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 avai­lable com­mands in your ver­sion of Ni­ko­la. Each and eve­ry one of those is a plu­gin. Let’s look at a ty­pi­cal exam­ple:

First, the serve.­plu­gin fi­le:

Name = serve
Module = serve

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


If you want to pu­blish your plu­gin on the Plu­gin In­dex, read the docs for the In­dex (and the .plu­gin file examples and ex­pla­na­tions).

For your own plu­gin, just change the va­lues in a sen­sible way. The Mo­dule will be used to find the mat­ching Py­thon mo­du­le, in this ca­se serve.­py, from which this is the in­ter­es­ting 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­tio­ned abo­ve, a plu­gin can have op­tions, which the user can see by doing ni­ko­la help com­mand and can la­ter use, for exam­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 want, real­ly. I have im­ple­men­ted a sort of pla­net using it. So, be crea­ti­ve, and if you do so­me­thing in­ter­es­ting, ­let me know ;-)

TemplateSystem Plugins

Ni­ko­la sup­ports Ma­ko and Jin­ja2. If you pre­fer some other tem­pla­ting ­sys­tem, then you will have to write a Tem­pla­te­Sys­tem plu­gin. He­re’s how they work. ­First, you have to create a .plu­gin file. He­re’s the one for the Ma­ko plu­gin:

Name = mako
Module = mako

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


If you want to pu­blish your plu­gin on the Plu­gin In­dex, read the docs for the In­dex (and the .plu­gin file examples and ex­pla­na­tions).

You will have to re­place “ma­ko” with your tem­plate sys­tem’s na­me, and other da­ta in the ob­vious ways.

The “Mo­du­le” op­tion is the name of the mo­du­le, which has to look so­me­thing like this, a stub for a hy­po­the­ti­cal sys­tem cal­led “Tem­pla­ter”:

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 real example in the Jin­ja plu­gin

Task Plugins

If you want to do so­me­thing that de­pends on the da­ta in your si­te, you ­pro­ba­bly want to do a Task plu­gin, which will make it be part of the ni­ko­la build com­mand. These are the cur­rent­ly avai­lable tasks, all ­pro­vi­ded by plu­gins:

$ nikola list
Scanning posts....done!

These have ac­cess to the site ob­ject which contains your ti­me­line and your confi­gu­ra­tion.

The cri­ti­cal bit of Task plu­gins is their gen_­tasks me­thod, which yields doit tasks.

The de­tails of how to handle de­pen­den­cies, etc., are a bit too much for this ­do­cu­ment, so I’ll just leave you with an exam­ple, the co­py_as­sets task. ­First the task_­co­py_as­sets.­plu­gin fi­le, which you should co­py and edit in the lo­gi­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 pu­blish your plu­gin on the Plu­gin In­dex, read the docs for the In­dex (and the .plu­gin file examples and ex­pla­na­tions).

And the task_­co­py_as­sets.­py fi­le, in its en­ti­re­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 plu­gins im­ple­ment mar­kup lan­gua­ges, they take sources for posts or pages and ­create HTML or other out­put files. A good example is the mi­sa­ka plu­gin.

They must pro­vi­de:

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

If the com­pi­ler pro­duces so­me­thing other than HTML fi­les, it should al­so im­ple­ment ex­ten­sion whi­ch ­re­turns the pre­fer­red ex­ten­sion for the out­put file.

These plu­gins can al­so be used to ex­tract me­ta­da­ta from a file. To do so, the ­plu­gin may im­ple­ment read_­me­ta­da­ta that will re­turn a dict contai­ning the ­me­ta­da­ta contai­ned in the file.

RestExtension Plugins

Im­ple­ment di­rec­tives for re­Struc­tu­red­Text, see me­dia.­py for a simple example.

If your out­put de­pends on a config va­lue, you need to make your post re­cord a ­de­pen­den­cy on a pseu­do-­pa­th, like this:


Then, whe­ne­ver the OP­TION­NAME op­tion is chan­ged in conf.­py, the file will be re­built.

If your di­rec­tive de­pends or may de­pend on the whole ti­me­line (like the post-­­list di­rec­ti­ve, where ad­ding new posts to the site could make it s­ta­le), you should re­cord a de­pen­den­cy on the pseu­do-­pa­th ####­­MA­­GIC####­­TI­­ME­­LINE.

MarkdownExtension Plugins

Im­ple­ment Mark­down ex­ten­sions, see mdx_­ni­ko­la.­py for a simple example.

Note that Py­thon mark­down ex­ten­sions are of­ten al­so avai­lable as se­pa­ra­te ­pa­ckages. This is on­ly meant to ship ex­ten­sions along with Ni­ko­la.

SignalHandler Plugins

These plu­gins ex­tend the Si­gnal­Hand­ler class and connect to one or mo­re ­si­gnals via blin­ker.

The ea­siest way to do this is to reim­ple­ment set_­si­te() and just connect to w­ha­te­ver si­gnals you want there.

Cur­rent­ly Ni­ko­la emits the fol­lo­wing si­gnals:

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 ni­ko­la de­ploy com­mand is run, and there is at least one new en­try/­post since last_­de­ploy. The si­gnal da­ta 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

When a post/­page is com­pi­led from its source to html, be­fore any­thing else is done with it. The si­gnal ­da­ta is in the form:

 '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 example is the de­ploy_­hooks plu­gin.

ConfigPlugin Plugins

Does no­thing spe­ci­fic, can be used to mo­di­fy the site ob­ject (and thus the config).

Put all the ma­gic you want in set_­si­te(), and don’t for­get to run the one ­from su­per­(). Example plu­gin: navs­to­ries

PostScanner Plugins

Get posts and sto­ries from “so­mew­he­re” to be ad­ded to the ti­me­line. The on­ly cur­rent­ly exis­ting plu­gin of this kind reads them from disk.

Plugin Index

There is a plu­gin in­dex, which stores all of the plu­gins for Ni­ko­la people wan­ted to share with the world.

You may want to read the README for the In­dex if you want to ­pu­blish your pa­ckage there.

Template Hooks

Plu­gins can use a hook sys­tem for ad­ding stuff in­to tem­plates. In or­der to use it, a plu­gin must re­gis­ter it­self. The fol­lo­wing hooks cur­rent­ly exist:

  • ex­tra­_­head (not equal to the config op­tion!)
  • bo­dy_end (not equal to the config op­tion!)
  • pa­ge_­hea­der
  • me­nu
  • me­nu_alt (right-­side me­nu in boots­trap, af­ter me­nu in ba­se)
  • pa­ge_­foo­ter

For exam­ple, in or­der to re­gis­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 ano­ther API avai­lable. It al­lows use of dy­na­mi­cal­ly ge­ne­ra­ted 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 se­cond ar­gu­ment to ap­pend() is used to de­ter­mine whe­ther the func­tion ­needs ac­cess to the cur­rent tem­plate context and the site. If it is set to True, the func­tion will al­so re­ceive site and context key­word ar­gu­ments. 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'!')


Some (ho­pe­ful­ly all) mar­kup com­pi­lers 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 crea­ting a plu­gin that ge­ne­rates mar­kup, it may be a good idea ­to re­gis­ter it as a short­code in ad­di­tion of to re­struc­tu­red text di­rec­tive or ­mark­down ex­ten­sion, thus ma­king it avai­lable to all mar­kup for­mats.

To im­ple­ment your own short­codes from a plu­gin, you can create a plu­gin in­he­ri­ting Short­co­de­Plu­gin and call Ni­ko­la.­re­gis­ter_­short­co­de(­na­me, func) with the fol­lo­wing ar­gu­ments:

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

The short­code hand­ler must ac­cept the fol­lo­wing na­med ar­gu­ments (or ­va­riable 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 ­pas­sed as na­med ar­gu­ments. Eve­ry­thing else will be pas­sed as po­si­tio­nal ar­gu­ments in the func­tion call.

So, for exam­ple:

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

As­su­ming you re­gis­te­red foo_­hand­ler as the hand­ler func­tion for the ­short­code na­med foo, this will re­sult in the fol­lo­wing call when the abo­ve ­short­code is en­coun­te­red:

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

Template-based Shortcodes

Ano­ther 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 ba­se­name and the ex­ten­sion .tm­pl. For exam­ple, if you ­want to add a new short­code na­med foo, create the tem­plate file as short­co­des/­foo.tm­pl.

When the short­code is en­coun­te­red, the mat­ching tem­plate will be ren­de­red wi­th its context pro­vi­ded by the ar­gu­ments gi­ven in the short­code. Key­word ar­gu­ments are pas­sed di­rect­ly, i.e. the key be­comes the va­riable name in the tem­pla­te ­na­mes­pace with a mat­ching string va­lue. Non-­key­word ar­gu­ments are pas­sed as s­tring va­lues in a tuple na­med _args. As for nor­mal short­codes with a ­hand­ler func­tion, site and da­ta will be ad­ded to the key­word ar­gu­ments.


The fol­lo­wing short­co­de:

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

With a tem­plate in short­co­des/­foo.tm­pl with this content (u­sing Jin­ja2 ­syn­tax in this exam­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

So­me­times your plu­gins will need to cache things to speed up fur­ther ac­tions. Here are the conven­tions for that:

  • If it’s a fi­le, put it so­mew­here in `self.­­site.­­con­fig['CA­­CHE_­­FOL­­DER']` (de­faults to `ca­che/`.
  • If it’s a va­lue, use `self.­site.­cache.­set(­key, va­lue)` to set it and `self.­site.­cache.­get(­key)` to get it. The key should be a string, the va­lue should be json-­en­co­dable (so, be ca­re­ful with da­te­time ob­jects)

The va­lues and files you store there can and will be de­le­ted so­me­times by the user. They should al­ways be ­things you can re­cons­truct wi­thout los­sage. They are thro­wa­ways.

On the other hand, so­me­times you want to save so­me­thing that is not a thro­wa­way. These are things that may ­change the out­put, so the user should not de­lete them. We call that state. To save sta­te:

  • If it’s a fi­le, put it so­mew­here in the wor­king di­rec­to­ry. Try not to do that please.
  • If it’s a va­lue, use `self.­site.state.­set(­key, va­lue)` to set it and `self.state.­cache.­get(­key)` to get it. The key should be a string, the va­lue should be json-­en­co­dable (so, be ca­re­ful with da­te­time ob­jects)

The `ca­che` and `s­ta­te` ob­jects are ra­ther sim­plis­tic, and that’s in­ten­tio­nal. They have no de­fault va­lues: if the key is not the­re, you will get `No­ne` and like it. They are meant to be both thread­sa­fe, but hey, who can ­gua­ran­tee that sort of thing?

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