PELICAN-THEMING(1)
π NAME π
okay so this is pelican-theming(1), aka the Pelican manual page about creating themes, and bestie itβs giving main character energy β¨ Pelican is the static site generator, themes are the fit it wears, and this page is the whole tutorial.
Thereβs a community-managed repository of Pelican Themes at https://github.com/getpelican/pelican-themes for people to share and use π«Ά
Please note that while we do our best to review and merge theme contributions, they are submitted by the Pelican community and thus may have varying levels of support and interoperability β no cap, thatβs just how community stuff works π
Community themes can also be found on PyPI tagged with Framework :: Pelican :: Themes at https://pypi.org/search/?q=&o=-created&c=Framework+%3A%3A+Pelican+%3A%3A+Themes π
π¦ CREATING THEMES π¦
To generate its HTML output, Pelican uses the Jinja templating engine β https://palletsprojects.com/p/jinja/ β because itβs flexible and the syntax is straightforward, periodt β¨ If you want to create your own theme, feel free to take inspiration from the "simple" theme at https://github.com/getpelican/pelican/tree/main/pelican/themes/simple/templates π
To generate your site using a theme you have created (or downloaded manually and then modified), you can specify that theme via the -t flag:
pelican content -s pelicanconf.py -t /projects/your-site/themes/your-theme
If youβd rather not specify the theme on every invocation, you can define THEME in your settings to point to the location of your preferred theme π
π§ Structure π§
To make your own theme, you must follow the following structure:
βββ static
β βββ css
β βββ images
βββ templates
βββ archives.html // to display archives
βββ article.html // processed for each article
βββ author.html // processed for each author
βββ authors.html // must list all the authors
βββ categories.html // must list all the categories
βββ category.html // processed for each category
βββ index.html // the index (list all the articles)
βββ page.html // processed for each page
βββ period_archives.html // to display time-period archives
βββ tag.html // processed for each tag
βββ tags.html // must list all the tags. Can be a tag cloud.
staticcontains all the static assets, which will be copied to the output theme folder. The above filesystem layout includes CSS and image folders, but those are just examples. Put what you need here πtemplatescontains all the templates that will be used to generate the content. The template files listed above are mandatory; you can add your own templates if it helps you keep things organized while creating your theme β¨
πΈ Templates and Variables πΈ
The idea is to use a simple syntax that you can embed into your HTML pages. This document describes which templates should exist in a theme, and which variables will be passed to each template at generation time π
All templates will receive the variables defined in your settings file, as long as they are in all-caps. You can access them directly π
π Common Variables π
All of these settings will be available to all templates.
output_fileβ The name of the file currently being generated. For instance, when Pelican is rendering the home page,output_filewill be"index.html"πarticlesβ The list of articles, ordered descending by date. All the elements areArticleobjects, so you can access their attributes (e.g.title,summary,authoretc.). Sometimes this is shadowed (for instance, in the tags page). You will then find info about it in theall_articlesvariable π₯datesβ The same list of articles, but ordered by date, ascending ποΈhidden_articlesβ The list of hidden articles πdraftsβ The list of draft articles πperiod_archivesβ A dictionary containing elements related to time-period archives (if enabled). See the section Listing and Linking to Period Archives for details ποΈauthorsβ A list of(author, articles)tuples, containing all the authors and corresponding articles (values) πcategoriesβ A list of(category, articles)tuples, containing all the categories and corresponding articles (values) π·οΈtagsβ A list of(tag, articles)tuples, containing all the tags and corresponding articles (values) πpagesβ The list of pages πhidden_pagesβ The list of hidden pages πdraft_pagesβ The list of draft pages π
β¨ Sorting β¨
URL wrappers β currently categories, tags, and authors β have comparison methods that allow them to be easily sorted by name:
{% for tag, articles in tags|sort %}
If you want to sort based on different criteria, Jinjaβs sort command at https://jinja.palletsprojects.com/en/latest/templates/#sort has a number of options π
ποΈ Date Formatting ποΈ
Pelican formats the date according to your settings and locale (DATE_FORMATS/DEFAULT_DATE_FORMAT) and provides a locale_date attribute. On the other hand, the date attribute will be a datetime object β https://docs.python.org/3/library/datetime.html#datetime-objects. If you need custom formatting for a date different than your settings, use the Jinja filter strftime that comes with Pelican. Usage is same as Python strftime format at https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior, but the filter will do the right thing and format your date according to the locale given in your settings:
{{ article.date|strftime('%d %B %Y') }}
π Checking Loaded Plugins π
Pelican provides a plugin_enabled Jinja test for checking if a certain plugin is enabled. This test accepts a plugin name as a string and will return a Boolean. Namespace plugins can be specified by full name (pelican.plugins.plugin_name) or short name (plugin_name). The following example uses the webassets plugin to minify CSS if the plugin is enabled and otherwise falls back to regular CSS:
{% if "webassets" is plugin_enabled %}
{% assets filters="cssmin", output="css/style.min.css", "css/style.scss" %}
<link rel="stylesheet" href="{{SITEURL}}/{{ASSET_URL}}">
{% endassets %}
{% else %}
<link rel="stylesheet" href="{{SITEURL}}/css/style.css}">
{% endif %}
π index.html π
This is the home page or index of your blog, generated at index.html π
okay so if pagination is active, the follow-up pages will live in index{number}.html β that's the pattern, bestie β¨
and here's the variable lineup for those index.html pagination vibes:
articles_paginatorβ a paginator object for the list of articles π«Άarticles_pageβ the current page of articlesarticles_previous_pageβ the previous page of articles (Noneif page does not exist)articles_next_pageβ the next page of articles (Noneif page does not exist)dates_paginatorβ a paginator object for the article list, ordered by date, ascending πdates_pageβ the current page of articles, ordered by date, ascendingdates_previous_pageβ the previous page of articles, ordered by date, ascending (Noneif page does not exist)dates_next_pageβ the next page of articles, ordered by date, ascending (Noneif page does not exist)page_nameβ'index'-- useful for pagination links π₯
πΈ author.html
this template gets processed for each of the existing authors, with output generated according to the AUTHOR_SAVE_AS setting (Default: author/{slug}.html). and yes, if pagination is active, subsequent pages will by default reside at author/{slug}{number}.html β she's consistent, periodt π
here's the whole roster for author.html, no cap:
authorβ the name of the author being processed β¨articlesβ articles by this authordatesβ articles by this author, but ordered by date, ascendingarticles_paginatorβ a paginator object for the list of articlesarticles_pageβ the current page of articlesarticles_previous_pageβ the previous page of articles (Noneif page does not exist)articles_next_pageβ the next page of articles (Noneif page does not exist)dates_paginatorβ a paginator object for the article list, ordered by date, ascending πdates_pageβ the current page of articles, ordered by date, ascendingdates_previous_pageβ the previous page of articles, ordered by date, ascending (Noneif page does not exist)dates_next_pageβ the next page of articles, ordered by date, ascending (Noneif page does not exist)page_nameβAUTHOR_URLwhere everything after{slug}is removed -- useful for pagination links π¦
π¦ category.html
Okay bestie, this one gets processed once for every category you've got, and the output lands wherever your CATEGORY_SAVE_AS setting says it should (Default: category/{slug}.html). And if pagination is active? Subsequent pages lowkey just live at category/{slug}{number}.html by default. β¨
Here's your variable lineup, no cap:
categoryβ the name of the category currently being processed πarticlesβ the articles for this categorydatesβ the articles for this category, but ordered by date, ascendingarticles_paginatorβ a paginator object for the list of articlesarticles_pageβ the current page of articlesarticles_previous_pageβ the previous page of articles (Noneif that page does not exist)articles_next_pageβ the next page of articles (Noneif that page does not exist)dates_paginatorβ a paginator object for the list of articles, ordered by date, ascendingdates_pageβ the current page of articles, ordered by date, ascendingdates_previous_pageβ the previous page of articles, ordered by date, ascending (Noneif that page does not exist)dates_next_pageβ the next page of articles, ordered by date, ascending (Noneif that page does not exist)page_nameβCATEGORY_URLwhere everything after{slug}is removed β super useful for pagination links π
That page_name one is genuinely such a slay for building your pagination links, iykyk.
πΈ article.html
This template gets processed for each article, with the output generated according to the ARTICLE_SAVE_AS setting (Default: {slug}.html). The following variables are available when rendering:
articleβ the article object to be displayedcategoryβ the name of the category for the current article
Now here's the fun part, bestie π β any metadata you put in the header of the article source file is literally available as fields on the article object. The field name is the same as the name of the metadata field, except in all-lowercase characters. So FacebookImage becomes facebookimage, and so on. Cute, right?
For example, you could add a field called FacebookImage to your article metadata, as shown below:
Title: I love Python more than music
Date: 2013-11-06 10:06
Tags: personal, python
Category: Tech
Slug: python-je-l-aime-a-mourir
Author: Francis Cabrel
FacebookImage: http://franciscabrel.com/images/pythonlove.png
This new metadata will be made available as article.facebookimage in your article.html template. This would allow you, for example, to specify an image for the Facebook open graph tags that will change for each article:
<meta property="og:image" content="{{ article.facebookimage }}"/>
Not me being obsessed with per-article og:images π it's giving main character energy fr fr.
π§ page.html
This template gets processed for each page, with output generated according to the PAGE_SAVE_AS setting (Default: pages/{slug}.html). The following variables are available when rendering:
pageβ the page object to be displayed. You can access itstitle,slug, andcontent. π«Ά
Short and sweet, just like a standalone page should be. π
π·οΈ tag.html
okay so this one gets processed once for every tag, and the output goes wherever your TAG_SAVE_AS setting says (default is tag/{slug}.html, slay). And if pagination is turned on? The later pages just chill at tag/{slug}{number}.html by default. periodt.
Here's everything you get to play with:
tagβ the actual name of the tag being processed, bestiearticlesβ all the articles that are related to this tagdatesβ same articles, same tag, but ordered by date, ascendingarticles_paginatorβ a paginator object for the list of articlesarticles_pageβ the current page of articlesarticles_previous_pageβ the previous page of articles (Noneif that page does not exist, tragic)articles_next_pageβ the next page of articles (Noneif that page doesn't exist either)dates_paginatorβ a paginator object for the list of articles, ordered by date, ascendingdates_pageβ the current page of articles, ordered by date, ascendingdates_previous_pageβ the previous page of articles, ordered by date, ascending (Noneif the page does not exist)dates_next_pageβ the next page of articles, ordered by date, ascending (Noneif the page does not exist)page_nameβ this one isTAG_URLwith everything after{slug}chopped off β super useful for pagination links, iykyk π
π
period_archives.html
This template gets processed for each year of your posts if a path for YEAR_ARCHIVE_SAVE_AS is defined, for each month if MONTH_ARCHIVE_SAVE_AS is defined, and for each day if DAY_ARCHIVE_SAVE_AS is defined. Very "every era gets its own scrapbook page" energy β¨
periodβ a tuple of the form(year, month, day)that says which time period we're on.yearanddayare numbers whilemonthis a string. This tuple only containsyearif the time period is a given year. It contains bothyearandmonthif the time period is over years and months, and so on.period_numβ a tuple of the form(year, month, day), exactly likeperiod, except all the values are numbers. no strings attached, literally π«Ά
You can see an example of how to use period in the "simple" theme period_archives.html template at https://github.com/getpelican/pelican/blob/main/pelican/themes/simple/templates/period_archives.html.
π Listing and Linking to Period Archives
The period_archives variable? iconic. It can be used to generate a list of links to the whole set of period archives that Pelican generates. And because it's a common variable, it's available for use in any template β so you can build that index in a custom direct template, or in a sidebar that's visible across different site pages. main character variable behavior, honestly π
period_archives is a dict that may contain year, month, and/or day keys, depending on which *_ARCHIVE_SAVE_AS settings are enabled. The corresponding value is a list of dicts, where each dict in turn represents a time period (ordered according to the NEWEST_FIRST_ARCHIVES setting) with these keys and values:
periodβ the same tuple as described inperiod_archives.html, e.g.(2023, 'June', 18).period_numβ the same tuple as described inperiod_archives.html, e.g.(2023, 6, 18).urlβ the URL to the period archive page, e.g.posts/2023/06/18/. This is controlled by the corresponding*_ARCHIVE_URLsetting.save_asβ the path to the save location of the period archive page file, e.g.posts/2023/06/18/index.html. This is used internally by Pelican and is usually not relevant to themes.articlesβ a list ofArticleobjects that fall under the time period.datesβ same list asarticles, but ordered according to theNEWEST_FIRST_ARCHIVESsetting.
Here is an example of how period_archives can be used in a template:
<ul>
{% for archive in period_archives.month %}
<li>
<a href="{{ SITEURL }}/{{ archive.url }}">
{{ archive.period | reverse | join(' ') }} ({{ archive.articles|count }})
</a>
</li>
{% endfor %}
</ul>
bestie you can literally just swap period_archives.month in that for statement for period_archives.year or period_archives.day and it works π
β pick whichever one matches the time-period granularity you actually want. level of detail is your call, no cap.
π¦ Objects
okay so this is the good part β the actual attributes you get to play with in your templates. important caveat tho: this is not the whole list, just a curated little selection of the ones that are genuinely useful in a template. so don't come for me if something's missing π
πΈ Article
the string representation of an Article is the source_path attribute. iconic.
authorβ the Author of this article β¨authorsβ a list of Authors of this articlecategoryβ the Category of this articlecontentβ the rendered content of the article πdateβ Datetime object representing the article datedate_formatβ either default date format or locale date formatdefault_templateβ default template namein_default_langβ Boolean representing if the article is written in the default languagelangβ language of the articlelocale_dateβ date formatted by thedate_formatmetadataβ Article header metadata dictsave_asβ location to save the article pageslugβ page slugsource_pathβ full system path of the article source filerelative_source_pathβ relative path fromPATH<settings.html#PATH> to the article source filestatusβ the article status, can be any of'published'or'draft'summaryβ rendered summary contenttagsβ list of Tag objectstemplateβ template name to use for renderingtitleβ title of the article πtranslationsβ list of translations Article objectsurlβ URL to the article page π₯
π Author / Category / Tag
these three are besties fr β the string representation of all of those objects is the name attribute. slay.
nameβ name of this object [1]page_nameβ author page namesave_asβ location to save the author pageslugβ page slugurlβ URL to the author page
[1] for the Author object, this comes from:authors:orAUTHOR. iykyk π
π Page π
Okay bestie, so the string representation of a Page is literally the source_path attribute. Hereβs the whole iconique attribute lineup, no cap:
authorβ the Author of this page.contentβ the rendered content of the page.dateβ a Datetime object representing the page date.date_formatβ either the default date format or the locale date format.default_templateβ default template name.in_default_langβ Boolean representing if the article is written in the default language.langβ language of the article.locale_dateβ date formatted by thedate_format.metadataβ page header metadata dict.save_asβ location to save the page.slugβ page slug.source_pathβ full system path of the page source file.relative_source_pathβ relative path fromPATH(settings.html#PATH) to the page source file.statusβ the page status, can be any of'published','hidden'or'draft'.summaryβ rendered summary content.tagsβ list ofTagobjects.templateβ template name to use for rendering.titleβ title of the page.translationsβ list of translationsArticleobjects.urlβ URL to the page.
β¨ Feeds β¨
So the feed variables changed in 3.0, bestie. Each variable now explicitly lists ATOM or RSS in the name. ATOM is still the default. Old themes will need to be updated. Hereβs the complete list of the feed variables:
AUTHOR_FEED_ATOM
AUTHOR_FEED_RSS
CATEGORY_FEED_ATOM
CATEGORY_FEED_RSS
FEED_ALL_ATOM
FEED_ALL_RSS
FEED_ATOM
FEED_RSS
TAG_FEED_ATOM
TAG_FEED_RSS
TRANSLATION_FEED_ATOM
TRANSLATION_FEED_RSS
π¦ Inheritance π¦
Since version 3.0, Pelican supports inheritance from the simple theme, so you can re-use the simple theme templates in your own themes. Slay.
If one of the mandatory files in the templates/ directory of your theme is missing, it will be replaced by the matching template from the simple theme. So if the HTML structure of a template in the simple theme is right for you, you donβt have to write a new template from scratch. Literally saving you so much time.
You can also extend templates from the simple theme in your own themes by using the {% extends %} directive as in the following example:
{% extends "!simple/index.html" %} <!-- extends the ``index.html`` template from the ``simple`` theme -->
{% extends "index.html" %} <!-- "regular" extending -->
πΈ Example πΈ
With this system, it is possible to create a theme with just one file. One. File. Obsessed.
π main.css π
The file is static/css/main.css (the default CSS_FILE):
body {
font-family : monospace ;
font-size : 100% ;
background-color : white ;
color : #111 ;
width : 80% ;
min-width : 400px ;
min-height : 200px ;
padding : 1em ;
margin : 5% 10% ;
border : thin solid gray ;
border-radius : 5px ;
display : block ;
}
a:link { color : blue ; text-decoration : none ; }
a:hover { color : blue ; text-decoration : underline ; }
a:visited { color : blue ; }
h1 a { color : inherit !important }
h2 a { color : inherit !important }
h3 a { color : inherit !important }
h4 a { color : inherit !important }
h5 a { color : inherit !important }
h6 a { color : inherit !important }
pre {
margin : 2em 1em 2em 4em ;
}
#menu li {
display : inline ;
}
#post-list {
margin-bottom : 1em ;
margin-top : 1em ;
}
π§ Custom templates π§
If you want to customise the templates as well, you can extend them. For example, to add a custom footer, create a templates/base.html:
{% extends "!simple/base.html" %}
{% block footer %}
<p>Custom footer</p>
{{ super() }}
{% endblock %}
- On the first line, we extend the
base.htmltemplate from thesimpletheme, so we donβt have to rewrite the entire file. - On the third line, we open the
footerblock which has already been defined in thesimpletheme. - On the fourth line, we insert the extra footer content.
- On the fifth line, the function
super()keeps the content previously inserted in theheadblock. - On the last line, we close the
footerblock.
This file will be extended by all the other templates, so the custom footer will appear on all pages. Main character energy for your footer, fr fr.
π Author π
The Pelican contributors
π Copyright π
2010β2026
Thatβs the whole page, bestie π«Ά β sourced from PELICAN-THEMING(1), dated Oct 04, 2026. Mwah! πβ¨
π¦ where this page comes from
- arch rolling: pelican 4.12.0-1 Β· AGPL-3.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms