Designing template

Typlog themes are powered by Jinja template language. If you are not familiar with Jinja, you can learn the syntax at the offical documentation.


We don't use {% extends %} in Typlog themes, each template inherits its own base layout which contains many usefull code in <head>. What we actually need are blocks:


Redesign the document title:

{% block title %}{{ page.title }}{% endblock %}


Adding more <meta>, <link> tag in meta block:

{% block meta %}
<meta />
{% endblock %}


Adding css in style block:

{% block style %}
<link href=",400,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ static_url }}hello.css">
{% endblock }


This is the main block for your theme. All content should be in this block.

{% block body %}
{% endblock %}


This block is located at the bottom of <body> tag, we can add some scripts here if needed:

{% block script %}
{% endblock %}


In the templates, we can use variables to render the real data into HTML. Here are all the variables:


This is a global variable, it contains the information about your site. The detail structure:

id: integer
name: string
slug: string
primary_lang: string
languages: [string, string]
base_url: uri
active: boolean
summary?: string
favicon?: uri
src: uri
width: integer
height: integer
src: uri
width: integer
height: integer
src: uri
width: integer
height: integer
license?: string
primary_links: [{ url, title, blank }]
secondary_links: [{ url, title, blank }]
color: string
socials: {}
sponsors: {}
list_url: string (e.g. /archive/)
posts_url?: string (e.g. /posts/, this attribute can be undefined)
audios_url?: string (e.g. /episodes/, this attribute can be undefined)
podcast?: # if enabled and has podcast information
title: string
subtitle: string
description: string
explicit: boolean
categories: []
type: string
image: uri
email: string
author: string
homepage: uri
keywords: string
copyright: string
links?: [ { icon, url, title, blank } ]

Please note, ? means this field may be None. For example:

{% if site.logo %}
{{ site.logo.src }}
{% endif %}


This is a global variable, it contains methods to query posts, episodes and etc.

.latest_subjects(subject_type=None, count=10, contains=None)

This will list the recent posts or episodes. This method is usually used in home.j2 template. The subject_type value can be post and audio.

Here is an example in home.j2:

{% block body %}
{% for post in query.latest_subjects('post') %}
<li>{{ post.title }}</li>
{% endfor %}
{% endblock %}

contains is a list, which can contain: authors, tags, and content.

If there is no need to show authors, tags or full content of a post, we can pass nothing to contains which will improve the performance of rendering.

.previous_subjects(subject, count=1, same_type=True, contains=None)

This method can list the previous posts or episodes of the given subject(post or episode). This is usually used in item.j2 template:

{% block body %}
<div class="previous">
{% for post in query.previous_subjects(page, count=2) %}
<a href="{{ post.url }}>{{ post.title }}</a>
{% endfor %}
{% endblock %}

.previous_subject(subject, same_type=True, contains=None)

This method will return only the nearest previous subject. It is used in item.j2 template.

.next_subjects(subject, count=1, same_type=True, contains=None)

This method is the same as query.previous_subjects, but for next.

.next_subject(subject, same_type=True, contains=None)

This method is the same as query.previous_subject, but for next.


This method will list all the tags in your site:

{% block body %}
{% for tag in query.tags() %}
<a href="{{ tag.url }}">{{ tag.title }}</a>
{% endfor %}
{% endblock %}


This method will list all the years of your posts or episodes.


This method will list all the authors, including hosts and guests in your site.


This is a global variable, it contains the features this site supports:

post: boolean
audio: boolean
photo: boolean
comment: boolean
subscriber: boolean
algolia: boolean


page is a context aware variable. It is different in each view.

In list.j2, the structure is:

title: string
url: path
type: string
topic: object
items: []
prev_year?: integer
prev_url?: path
next_year?: integer
next_url?: path

A topic refers to a model about the list page. For instance, in a tag page: /tags/web, this topic is the tag object, which means you can set:

{% set tag = page.topic %}

This topic is usually used together with type. Here is an example of how to use it:

{% macro render_tag(tag) %}
{% endmacro }
{% macro render_user(author) %}
{% endmacro }
{% block body %}
{% if page.type == 'tag' %}
{{ render_tag(page.topic) }}
{% elif page.type == 'author' %}
{{ render_tag(page.topic) }}
{% elif page.type == 'audio_list' %}
{{ render_podcast_info(site) }}
{% else %}
{{ render_site_info(site) }}
{% endif %}
{% endblock }

items is a list of posts and episodes of current visiting page.

In item.j2, page refers to a post, an episode, or a page object.

id: integer
type: string # post, audio, page
slug: string
lang: string
title: string
src: uri
width: integer
height: integer
subtitle?: string
content: string
url: path
tags: array of tags
authors: array of authors
published_at: datetime
updated_at: datetime
comment: string # open, closed, disabled
visibility: string # public, password, member
render_html: function # render_html(lazy=True) -> HTML
# only available with "post" type
license: string
# only available with "audio" type
image: uri
explicit: boolean
season: integer
episode: integer
episode_type: string
duration: string
src: uri
size: integer
duration: integer
mimetype: string

In episode post, page contains two more attributes:

# ....
hosts: array of authors
guests: array of authors


This variable will be a jsdelivr link. In development, serve-theme will replace this variable to the local css file. It is usually used in:

{% block style %}
<link rel="stylesheet" href="{{ static_url }}hello.css">
{% endblock %}


This is not a variable, but it may be used via:

  1. query.tags()
  2. page.topic of tag type in list.j2
  3. page.tags in item.j2

The structure of a tag:

id: integer
slug: string
title: string
summary: string
src: uri
width: integer
height: integer
metadata: {}
url: path


This object may be one of these:

  1. query.authors()
  2. page.topic of author type in list.j2
  3. page.authors in item.j2

The structure of a author:

username: string
name: string
src: uri
width: integer
height: integer
avatar: uri
bio: html
metadata: {}
url: path
# only in page.authors
role: string # primary, secondary


We can use all the built-in filters provided by Jinja. And in Typlog, there are two more filters:


This filter is used to produce ISO formattted datetime, it is usually used to format .published_at:

<time datetime="{{ page.published_at|xmldatetime }}">

This is equal to:

<time datetime="{{ page.published_at.strftime('%Y-%m-%dT%H:%M:%SZ') }}">


This filter is used to resize the images, it accepts several image sizes:

<img src="{{ site.logo.src|thumbnail('ss') }}">

Available sizes:

  • s: small size
  • m: middle size
  • l: large size
  • ss: crop to squared small size
  • sm: squared middle size
  • sl: squared large size


There are some built-in macros, which can be used to help you rendering the templates.

{% from "macros.j2" import render_social_icons %}
This macro is used for:
{{ render_social_icons(site) }}
{% from "macros.j2" import render_item, render_navigation %}
The `render_item` is usually used in `home.j2` and `list.j2`.
{% for item in query.latest_subjects() %}
{{ render_item(site, item) }}
{% endfor %}
The `render_navigation` macro is used in `list.j2` to render the year navigation:
{{ render_navigation(site, page) }}
{% from "macros.j2" import render_subject_visibility, render_subject_content %}
{% from "macros.j2" import render_subject_license, render_review_subject, render_subject_authors %}
These macros are used in `item.j2`. The `render_subject_content` is used to render the content part, it will
handle visibility automatically.
{{ render_subject_content(site, page) }}
Parameters for other macros:
{{ render_subject_visibility(site, page) }}
{{ render_subject_license(site, page) }}
{% if %}
{{ render_review_subject( }}
{% endif %}
{{ render_subject_authors(site, page) }}


Here are the built-in snippets which we can {% include %} them:

this is typlog brand foot
{% include "_snippets/brand_foot.j2" }
this is your site foot
{% include "_snippets/site_foot.j2" }

Used in item.j2:

{% include "_snippets/subject_share.j2" %}
{% include "_snippets/subject_foot.j2" %}

JavaScript hooks

In the rendered html, Typlog will inject a typlog.js script. This script has many features, we can use js-? class to connect the hooks.

js-search: popup the search overlay

<input class="js-search" type="text" placeholder="Search..."
{% if features.algolia %}data-id="{{ site.algolia_id }}" data-key="{{ site.algolia_key }}"{% endif %}>

js-subscribe: popup the subscribe dialog

<button class="js-subscribe">Subscribe</button>

js-audio: render the player UI

<div class="entry-audio js-audio">
<audio src="{{ }}" preload="none" controls
{% if page.image %}data-image="{{ page.image|thumbnail('ss') }}"{% endif %}>
This hook is already included in
{{ render_subject_content(site, page) }}

js-enjoy: click to send like event

<button class="js-enjoy">Enjoy</button>
This hook is already included in snippet:
{% include "_snippets/subject_share.j2" %}

Next step

Now, your theme looks pretty, and you are eager to use this theme, you have to submit the theme to Typlog theme registry.

Read more