the long and the shortcodes of it

I've just spent a little time trying to learn how to use Nikola's "shortcodes", and I've learned enough delicate things that I want to write them down for later. Not least, there are descriptions of the shortcode syntax on two different very long and identically-formatted pages, the Nikola handbook and the guide to extending Nikola.

There's also some overlap between the Markdown-supported shortcodes versus a set of reStructuredText extensions.

Built-in shortcodes

The short version is that there are some magic words that you can include in a Markdown content file, using something like Jinja's template syntax, which do programmy things. One is raw, which doesn't seem to be able to display itself because of some recursion thing. But in general,

{{% name parameters %}}

makes things happen. There are a number of built-in shortcodes, whose examples in the manual I won't duplicate, but I'll list them here:

  • chart
    This actually draws a graph in the page, which would be a fun thing to play with later.

    Browser usage evolution (in %) 00101020203030404050506060707080802002200320042005200620070241.18397435897435504.11538461538464200416.6346.5044871794872456.3051819974897200525451.825432.11206741975974200631557.1455128205129414.83127129280973200785.876.88397435897436257.0200284.6182.20448717948716260.45615922539200384.7287.525260.1681459566075200474.5392.84551282051285289.54549937242246200566498.1660256410256314.0266272189349200658.6603.4865384615385335.33960910883985200714.2100.05448717948718463.2175004482697200215.4205.375459.7613412228797200315.3310.6955128205128460.049354491662220048.9416.0160256410257478.482203693742220059521.3365384615385478.19419042495963200610.4626.6570512820514474.16200466200472007Browser usage evolution (in %)FirefoxChromeIEOthers

  • doc
    This seems to be the correct way to do internal linking, using the stub names. For example, My previous post was improbable units.
  • emoji
    😢
  • listing
    p-exam/poisson.py (Source)
    # within ipython --pylab
    from scipy.special import factorial
    top = 5
    def p(x, mean):
        return exp(-mean)*mean**x / factorial(x)
    x, mean = meshgrid( arange(2*top+1), linspace(0.5,top,12) )
    pp = p(x,mean)
    gcf().clear()
    plot(x.T,pp.T, alpha=0.5, marker='.');
    legend([f"mean={m:.2f}" for m in unique(mean)]);
    xlabel('x') ; ylabel('P(x)');
  • media

    It doesn't seem to work magically for embedding a local video file.

  • thumbnail
    I haven't messed with images, but I think there's some magic thumbnailing happening. Perhaps this is how I'll use it.
  • template
    This lets you put jinja template code right in the document.
    • The square of 6 is 36
    • The square of 7 is 49
    • The square of 8 is 64
    • The square of 9 is 81

Custom template shortcodes

A template file named shortcodes/mycode.tmpl will be executed when the source text contains

{{% foo bar=bla baz bux  %}}

Here's a little template that implements foo:

shortcodes/foo.tmpl (Source)

    This fragment should appear in the text.
    <ul>
    {% for number in range(6,10) %}
    <li>The square root of {{number}}
        is {{ '{:0.3f}'.format(number**0.5) }}</li>
    {% endfor %}
    </ul>

And here's what it does:

This fragment should appear in the text.

  • The square root of 6 is 2.449
  • The square root of 7 is 2.646
  • The square root of 8 is 2.828
  • The square root of 9 is 3.000

Exploration by introspection

shortcodes/introspection.tmpl (Source)

This template prints some information about the <code>post</code> object.

<table style="font-family: monospace; font-size: xx-small;">
  <tr>
    <th> key </th>
    <th> type </th>
    <th> size </th>
  </tr>
  {% for key in post.__dict__  %}
    <tr>
      <td>{{ key }}</td>
      <td>{{ post[key].__class__.__name__ }}</td>
      {% if post[key].__class__ == post['compile_html'].__class__ %}
        <td> callable method </td>
      {% elif post[key].__class__ == post['messages'].__class__ %}
        <td> [something complicated] </td>
      {% elif post[key].__class__ in (dict, list, tuple) %}
        <td>{{ post[key] | length }}</td>
      {% else %}
        <td>{{ post[key] }}</td>
      {% endif %}
    </tr>
  {% endfor %}
</table>

This template prints some information about the post object.

key type size
config dict 204
current_time datetime 2025-09-30 23:58:13.299937-05:00
base_url str https://mahurin.us/robtasm/
strip_indexes bool True
index_file str index.html
pretty_urls bool True
default_lang str en
translations dict 1
skip_untranslated bool False
_default_preview_image NoneType None
types_to_hide_title list []
source_path str posts/2025/07/03/the-long-and-the-shortcodes-of-it.md
post_name str posts/2025/07/03/the-long-and-the-shortcodes-of-it
base_path str cache/posts/2025/07/03/the-long-and-the-shortcodes-of-it.html
_base_path str cache/posts/2025/07/03/the-long-and-the-shortcodes-of-it.html
metadata_path str posts/2025/07/03/the-long-and-the-shortcodes-of-it.meta
compiler CompileMarkdown
is_post bool True
messages Functionary [something complicated]
_template_name str post.tmpl
compile_html method callable method
demote_headers int 1
_dependency_file_fragment defaultdict defaultdict(, {'en': [(True, .create_lambda.. at 0x7f0d61bc8220>)], None: []})
_dependency_file_page defaultdict defaultdict(, {'en': [], None: []})
_dependency_uptodate_fragment defaultdict defaultdict(, {'en': [], None: []})
_dependency_uptodate_page defaultdict defaultdict(, {'en': [], None: []})
_depfile defaultdict defaultdict(, {})
metadata_extractors_by dict 4
translated_to set {'en'}
folder_relative str 2025/07/03
folder_base TranslatableSetting posts
folders dict 1
folder str posts/2025/07/03
_is_two_file bool False
meta Functionary [something complicated]
used_extractor Functionary [something complicated]
date datetime 2025-07-03 17:03:10-05:00
updated datetime 2025-07-03 17:03:10-05:00
data Functionary [something complicated]
category_from_destpath bool False
_tags dict 1
publish_later bool False
use_in_feeds bool True
url_type NoneType None
_next_post Post
_prev_post Post

Now that I know how to poke around on the inside of these things, I am much more likely to be able to figure out how they work.