Creating themes

What every template can see

A template never fetches anything. It is handed a bundle of ready-to-use values — FolioPress calls it the context — and prints what it needs. Part of the bundle is the same on every page; the rest depends on which template is rendering.

Shared by every template

Variable What it is

page The page or post being viewed, already resolved to the current language (see below). None on the tag archive and search.

site_title, site_description, footer From Site Settings, in the current language. footer is already rendered from Markdown.

navigation_menu The menu configured in the admin: a list of {title, url, slug, page_id}, each url ready to use in this language.

languages The site’s languages, keyed by code. Each entry: code, name, native_name, short_code, menu_label, flag.

current_language, current_language_code, default_language_code The language being rendered (same shape as a languages entry), its code, and the site’s primary language.

language_switch_urls Keyed by language code: the URL of this same page in that language, translated slug and all.

home_url, url_language_code The home page in the current language (/ or /pt-pt), and the lowercase prefix to put in front of any URL you build yourself ('' for the primary language).

static_url, media_url Where this theme’s files are served from (/static/<theme>), and where uploaded images live.

page_title, meta_description, canonical_url, hreflang_urls, social_image_url, twitter_card_type, json_ld Everything the <head> needs: a sensible tab title, absolute canonical and per-language alternate URLs, an Open Graph image, and structured data — all precomputed.

theme_name, theme_info, theme_settings The active theme’s name, its whole theme.yml, and its settings block.

t The theme’s own translator: {{ t('Read more') }} looks the phrase up in locales/<language>.json (see below).

request The FastAPI request, for the rare case you need the path or a query parameter.

The page object

The same shape whether it is a page, a post, or an entry in any list of posts:

Field Notes

title, slug, url In the current language. url is complete, including the language prefix or the language’s own domain.

content Already converted from Markdown to HTML — print it with {{ page.content | safe }}.

excerpt, meta_description Short forms, in the current language.

poster_image_url Full URL of the poster image, or None.

gallery, content_gallery Lists of images: {id, url, title, width, height, original_filename}. Use image.url directly.

tags {slug, title, url} for each public tag on it. Private tags are never included.

is_post, is_featured, published_at, created_at, updated_at Whether it is a post, whether it is featured, and its dates.

id The database id — handy for anchors and cache keys.

Lists of posts

Template Variable Contents

home.html posts (alias recent_posts), pages, is_home Posts sharing a tag with the home page, sized and ordered by theme.yml’s home section. A home page with no tags receives no posts — tag it to fill it.

page.html posts (alias page_posts) Posts sharing a tag with this page, from the page section. This is how a “Blog” page lists its posts.

post.html related_posts, is_post Other posts sharing a tag with this one, from the post section.

tag.html tag (title, slug), posts Every published post carrying this tag, from the tag section.

search.html search_query, search_results, results_count The term entered and the matching pages and posts.

Every list of posts comes with pagination in the same shape: has_pagination, current_page, total_pages, has_previous, has_next, previous_page, next_page, and pagination_urls with first, previous, next and last — None where there is nowhere to go. When has_pagination is off in theme.yml, the list is simply cut at posts and the flags stay false.

Languages and the switcher

Two of the language fields are computed for you. short_code is the language code alone — EN — unless the site also enables another variant of the same language, in which case it keeps its region: EN-US and EN-GB. menu_label follows the same rule with names: English, or English (US) and English (UK). This is the exact rule the admin’s own language switcher uses, so the site and the admin never disagree about a label. A complete switcher is therefore short:

{% if languages | length > 1 %} <button type="button" aria-label="{{ t('Change language') }}"> {{ current_language.short_code }} </button> <ul> {% for lang in languages.values() | sort(attribute='menu_label') %} <li> {% if lang.code == current_language.code %} <span aria-current="true">{{ lang.menu_label }}</span> {% else %} <a href="{{ language_switch_urls[lang.code] }}" hreflang="{{ lang.code }}">{{ lang.menu_label }}</a> {% endif %} </li> {% endfor %} </ul> {% endif %}

language_switch_urls already points at the same page in each language — the translated slug, or the home page if that translation does not exist yet — so a switcher never builds a URL itself.

Still need help? Write to us from your site's admin (Support), or email support@foliopress.io.