Publ: Templating Guide

Last updated:

Publ templates use the Jinja2 templating system; please see its references for the general syntax of template files.

There are three kinds of page in Publ: entry, category, and error.

How templates work

Short version

When someone requests a page, Publ finds the most-specific template that matches, and uses that to render the page.

Long version

When someone requests a page, Publ looks up whether it’s a category view or an entry. Category views look something like or Entry views look something like

Every view has a category associated with it; for example, is in the art/photos category (i.e. all the stuff between the website address and the entry ID, not including the /s at either end), and is in the blog category; the category is basically everything between the server name and the last / (in this case, minimal is the view).

When you see an entry, there is only one possible template that it chooses, entry. When you see a category, if there’s a view indicated, it uses that view name; otherwise it uses the index view.

(The category can be blank, incidentally; shows you the (index view on the empty category.)

Anyway, given the category and view name, Publ looks for the closest matching template, by starting out in the template directory that matches the category name, and then going up one level until it finds a matching template. And a template will match based on either the exact name, or the name with .html, .htm, .xml, or .json added to the end.

So for example, if you ask for, it will look for a template file named feed, feed.html, feed.htm, feed.xml, or feed.json, in the directories templates/music/classical/baroque, templates/music/classical, templates/music, and templates, in that order, returning the first one that matches. (If no templates match, it shows an error page.)

Note that the default names of entry and index can be overridden on a per-entry or per-category basis.

Error templates

A note on error templates: Error pages generally get handled by whatever matches the error template; however, in the case of a specific status code, it will also look for a template named based on that code and its category. For example, a 404 error will try to render the 404 template first, then try 400, then finally error. (And of course this can be 404.html, 404.xml, 404.json, and so on, although in most cases you’ll probably be doing it as HTML.)

Generating CSS files

You can also use the template system to generate CSS, which is useful for having your CSS files reference other assets via static() or image(). This also makes it quite simple to create CSS templates that inherit from and override other CSS templates; for example, if you have the file templates/style.css, you can have templates/blog/style.css that looks like:

@import url('../style.css');

body { background: red; }

and then your HTML templates need only refer to the stylesheet using, for example:

<link rel="stylesheet" href="style.css" />

Required templates

Technically no template is actually required, but in order to have a functioning site, you should have, at the very least, the following top-level templates:

  • index.html: the default category view
  • entry.html: the entry view
  • error.html: Some sort of error page (although this is technically optional)

Template naming and overrides

Publicly-visible templates' names must start with a letter, and should only consist of URL-friendly characters (alphanumerics, periods, dashes, and underscores).

Numeric error templates are not considered publicly-visible.

If a template’s name begins with _ the template will be considered “private;” it will be available internally (such as to Entry-Template,Index- Template, or get_template()) but it will not be available to the world at large. So, for example, if you want to override your site’s main page you can create a file like /content/

Name: My website
Index-Template: _main_page

which will cause it to render with the template templates/_main_page.html, but will not allow other URLs e.g. /blog/_main_page to use this template.

Simiarly, it is highly recommended that Entry-Template overrides use this convention, as it usually does not make sense to render an entry template in a category context.

Template API

As mentioned before, templates are rendered using Jinja2, and get standard template parameters for both Jinja2 and Flask. Additionally, there will be other things available to you, depending on the template type.

Also, note that the templating system uses Python syntax for passing parameters to functions. So, for example, some_function(1,dingle=True,berry="none") passes a number, a boolean (true/false) named dingle, and a string named berry with the value of "none". When passing strings, quotes are required. When passing anything else, the quotes should be left off; False and "False" mean very different things in Python. (For starters, "False" is equivalent to True in many contexts.)

All templates

All template types get the default Flask objects; there is more information about these on the Flask templating reference.

The following additional things are provided to all templates:

  • arrow: The Arrow time and date library

  • get_view: Requests a view of entries; see the view documentation for the supported arguments.

  • static: Build a link to a static resource. The first argument is the path within the static resources directory; it also takes the following optional named arguments:

    • absolute: Whether to force this link to be absolute
      • False: Use a relative link if possible (default)
      • True: Use an absolute link
  • get_template: A function that finds a template file for a given category or entry. The first argument is the name of the template to find or a list of template names; the second argument is what to find the template relative to.

    Example usage:

    {% for entry in view(recurse=True).entries %}
        {% include get_template('_entry', entry) %}
    {% endfor %}

    This fragment will find all of the entries below the current category, and then render the closest matching _entry template for that category, with the filenames mapped as described above. This is useful for having content where its appearance within the template changes based on the category it’s in; for example, if you want your Atom feed to show full content for only some parts of your site, or if you want image thumbnails to generate differently.

    Note: The included template will only see the variables that are visible at that point in the template. If you need to set a variable that would normally be visible to the template, use the Jinja {% set %} command; for example, if the template fragment expects to get a variable named subcat and you want it to refer to the category, you’d do something like:

    {% for cat in category.subcats(recurse=True) %}
        {% set subcat = cat %}
        {% include get_template('_my_fragment', cat) %}
    {% endfor %}
  • template: Information about the current template

  • image: Load an image

    The resulting image works as a URL directly, or you can pass in arguments in the format (output_scale, [arguments]), which gets a URL rendered with the specified output scaling and image rendition arguments. The default is to use an output_scale of 1.

    It also has the following functions on it:

    • get_img_tag([arguments]): Makes an <img> tag with the provided image rendition arguments, with the following additions:

      • alt_text: Provides text for the img’s alt attribute
      • title: Provides text for the img’s title attribute
    • get_css_background([arguments]): Makes the appropriate background-image CSS attributes with the provided image rendition arguments, with the following additions:

      • uncomment: Whether the CSS declaration will be wrapped inside a comment; this is useful if your code editor would otherwise get confused by Jinja declarations inside of CSS.
    • get_image_attrs([arguments]): Generates the raw attributes that would be applied to the <img> tag produced by get_img_tag. This is probably not useful for templating, however.

    Example usage in HTML templates:

    <div style="{{ image('/layout/bg.png').get_css_background(width=320) }}"> ... </div>
    {{ image('').get_img_tag(link='') }}

    and in a CSS template:

    body {
        /* {{image("/background.jpg").get_css_background(width=320,height=320,uncomment=True)}} */
        background-color: white;

    Like the entry Markdown tags, you can use a @ prefix to indicate that the image comes from the static files (rather than from the template’s search path), and external URLs work as well.

    Unlike the entry Markdown tags, you have to specify width and height by name.

    If the image path is absolute (i.e. starts with a /), it will be treated as based on the site’s content directory. If it is relative, it will look in the following locations, in the following order:

    1. Relative to the current entry’s file (on entry templates)
    2. Relative to the current entry’s category within the content directory (on entry templates)
    3. Relative to the current category within the content directory
    4. Relative to the template file, mapped to the content directory (for example, the template templates/blog/feed.xml will search in content/blog)

A note to advanced Flask users: while url_for() is available, it shouldn’t ever be necessary, as all its useful functionality is exposed via the available objects. However, if you really want to write directly to an endpoint (or have extended the app with your own blueprints or other Flask modules/routes/etc.), here are the endpoints thatPubl makes available:

  • category: Routes to a category page; options are:
  • entry: Routes to an entry page; options are:
    • entry_id: The numeric ID of the entry (this is the only one that’s useful to specify in url_for)
    • slug_text: The SEO slug text
    • category: The category the entry lives in

Entry pages

Specific entries are always rendered with its category’s entry template. The template gets the following additional objects:

  • entry: Information about the entry
  • category: Information about the category

Category pages

Categories are rendered with whatever template is specified, defaulting to index if none was specified. The template gets the following additional objects:

  • category: Information about the category
  • view: The default view for this category. It is equivalent to calling get_view() with the following arguments:

    • category: This category
    • recurse: False
    • date, first, last, before, after: set by the URL query parameters

    Note that unless any limiting parameters are set, this view object will not have any pagination on it. The intention is that view will be used as a basis for another more specific view; for example, this example will show 10 entries at a time and get previous and next as appropriate:

    {% set paged_view = view(count=10) %}
    {% for entry in paged_view.entries %}
        <!-- render entry -->
    {% endfor %}
    {% if paged_view.previous %}
        <a href="{{}}">Previous page</a>
    {% endif %}
    {% if %}
        <a href="{{}}">Next page</a>
    {% endif %}

Error pages

Error templates receive an error object to indicate which error occurred; otherwise it only gets the default stuff. This object has the following properties:

  • code: The associated HTTP error code
  • message: An explanation of what went wrong
  • category: Information about the category
  • exception: In the case of an internal error, this will be an object with the following properties:

    • type: The human-readable type of exception (IOError, ValueError, etc.)
    • str: The human-readable exception string
    • args: Further information passed to the exception constructor