Pandoc Partial Templates

Most people know about Pandoc from its fantastic ability to convert various markup formats from one to another. A little less obvious is Pandoc can be a template engine for rendering static websites allowing you full control over the rendered content.

The main Pandoc documentation of the template engine can be found in the User Guide. The documentation is complete in terms of describing the template capabilities but lacks a tutorial for using as a replacement for more ambitious rendering systems like Jekyll or Hugo. Pandoc takes a vary direct approach and can be deceptively simple to implement.

Use your own template

First thing in this tutorial is to use our own template with Pandoc when rendering a single webpage. You use the –-template option to provide your a template name. I think of this as the page level template. This template, as I will show later, can then call other partial templates as needed.

Example, render the Pandoc-Partials.txt file using the template named index1.tmpl:


       pandoc --from=markdown --to=html \
           --template=index1.tmpl Pandoc-Partials.txt > index1.htm
   

This is a simple template page level template.


       <!DOCTYPE html>
       <html>
       <head>
       </head>
       <body>
       ${body}
       </body>
       </html>
   

When we run our Pandoc command the file called Pandoc-Partials.txt is passed into the template as the “body” element where it says ${body}. See this Pandoc User Guide for the basics.

Example 1 rendered: index1.htm

Variables and metadata

Pandoc’s documentation is good at describing the ways of referencing a variable or using the built-in template functions. Where do the variables get their values? The easiest way I’ve found is to set the variables values in a JSON metadata file. While Pandoc can also use the metadata described in YAML front matter Pandoc doesn’t support some of the other common front matter formats. If you’re using another format like JSON or TOML for front matter there are tools which can split the front matter from the rest of the markdown document. For this example I have created the metadata as JSON in a file called metadata.json.

Example metadata.json:


       {
           "title": "Pandoc Partial Examples",
           "nav": [
               {"label": "Pandoc-Partials", "href": "Pandoc-Partials.html" },
               {"label": "Version 1", "href": "index1.htm" },
               {"label": "Version 2", "href": "index2.htm" },
               {"label": "Version 3", "href": "index3.htm" }
           ]
       }

Let’s modify our initial template to include our simple navigation and title.

Example index2.tmpl:


       <!DOCTYPE html>
       <html>
       <head>
         ${if(title)}<title>${title}</title>${endif}
       </head>
       <body>
       <nav>
       ${for(nav)}<a href="${it.href}">${it.label}</a>${sep}, ${endfor}
       </nav>
       <section>
       ${body}
       </section>
       </body>
       </html>
   

We would include our navigation metadata with a Pandoc command like


       pandoc --from=markdown --to=html \
              --template=index2.tmpl \
              --metadata-file=metadata.json Pandoc-Partials.txt > index2.htm
   

When we render this we now should be able to view a web page with simple navigation driven by the JSON file as well as the body content contained in the Pandoc-Partials.txt file.

Example 2 rendered: index2.htm

Partials

Sometimes you have more complex documents. Putting this all in one template can become tedious. Web designers use a term called “partials”. This usually means a template for a “part” of a page. In our initial example we can split our navigation into it’s own template.

Implementing partials

Pandoc will look in the current directory for partials as well as in a sub directory called “templates” of the current direct. In this example I am going to include my partial template for navigation in the current directory along side my index3.tmpl. My navigation template is called nav.tmpl.

Here’s my partial template:


       <nav>
       ${for(nav)}<a href="${it.href}">${it.label}</a>${sep}, ${endfor}
       </nav>
   

Here’s my third iteration of our index template, index3.tmpl.


       <!DOCTYPE html>
       <html>
       <head>
       ${if(title)}<title>${title}</title>${endif}
       </head>
       <body>
       ${if(nav)}
       ${nav.tmpl()}
       ${endif}
       <section>
       ${body}
       </section>
       </body>
       </html>
   

Pandoc only requires you to reference the partial by using its base name. Many people will name their templates with the extension “.html”. I find this problematic as if you’re trying to list the templates in the directory you can not easily list them separately. I use the “.tmpl” extension to identify my templates. Since I have other documents that share the base name “nav” I explicit call my navigation partial using the full filename followed by the open and closed parenthesis. I have also chosen to wrap the template in an “if” condition. That way if I don’t want navigation on a page I skip defining it in my metadata file.

Inside the partial template we inherit the parent metadata object. You can use all the built-in Pandoc template functions and variables provided by Pandoc in your partial templates.

Putting it all together:


       pandoc --from=markdown --to=html \
              --template=index3.tmpl \
              --metadata-file=metadata.json Pandoc-Partials.txt > index3.htm
   

Example 3 rendered: index3.htm