Al Sweigart's Opinionated Pelican Tutorial
Posted by Al Sweigart in misc
This is an opinionated tutorial for the Pelican static blog generator. Writing a tutorial forces me to fill in all the gaps of my knowledge. The main reason I'm writing this tutorial is because I want to get a solid understanding of Pelican for making my website.
I'm using Pelican to create new versions of InventWithPython.com and AutomateTheBoringStuff.com. Pelican is written in Python and has a balance of helpful features while remaining simple. A static blog generator turns the source content files (written as .md Markdown files) for your blog posts and web pages into .html HTML files that you can upload to your web host. This has the benefit of being simple, fast, secure, and not needing a database. The downside is that you cannot have backend-features like a comments section. This is fine by me; the moderation and spam management outweighs the benefits.
For almost twenty years I've used Dreamhost for cheap and reliable web hosting. I've used Let's Encrypt for free HTTPS certificates. (HTTPS requires I get a static IP address from Dreamhost, which is an extra few dollars a month. It's worth it. In 2025, HTTPS is a necessity, not a nice-to-have.) Unless I say otherwise, I only use free software tools and services in this tutorial.
(TODO NOTE: This tutorial is incomplete, but I didn't want it sitting on my hard drive. I'll eventually finish it.)
Creating a Project Folder and Installing Pelican
From my GitHub profile, I create a new repository called inventwithpythondotcom
. I like using the "dotcom" or "dotorg" suffix in my git repo names for websites. I often set these as private repos unless I want the public to be able to submit PRs ("pull requests") to do typo fixes. Using a git repo for version control provides an online backup and lets me see previous versions of my files. GitHub has a git tutorial you can read.
I have a folder named gitrepos in my Macbook's home folder that I store all my git repos in. From a Terminal window and in the gitrepos folder, I run git clone [email protected]:asweigart/inventwithpythondotcom.git
(I got this text from the repo page on GitHub under the green Code button.) This clone command copies the empty git repo to my computer in a ~/gitrepos/inventwithpythondotcom.
On my Macbook, my home folder is /Users/al and the shortcut way of referencing your home folder is with a tilde: ~. I put all my git repositories in ~/gitrepos such as ~/gitrepos/inventwithpythondotcom. I'm going to call this my project folder in this tutorial. Unless I say otherwise, I'll pretend that my Terminal window is in this folder so that all the file paths will be like ./example/index.html will refer to /Users/al/gitrepos/inventwithpythondotcom/example/index.html.
I will use ./ to refer to the project folder in this tutorial.
I installed Hatch so that I could easily create and manage Python virtual environments. Using the Terminal window in the inventwithpythondotcom folder, I create a virtual environment by running hatch shell
. The Terminal prompt changes al@almachine ~ %
to (inventwithpythondotcom) al@almachine ~ %
to show that the virtual environment is "activated" in this Terminal window.
The current version of Pelican is 4.11.0. I install Pelican in this virtual environment by running pip install "pelican[markdown]"
(which also installs the Markdown extensions.) I find it easier to write my webpages in Markdown than HTML. The tutorial on markdownguide.org seems decent.
The nice thing about Markdown files is that it supports raw embedding of HTML. This means you can put regular HTML tags in your Markdown files and they automatically work. Markdown files are plaintext files, meaning you can edit them with a text editor like Sublime Text or Visual Studio Code. The file extension for Markdown files is .md.
Side Note: I sometimes copy/paste my Markdown-formatted text to Dillinger as my Markdown WYSIWYG ("wizz-ee-wig", or What You See Is What You Get) editor to make sure I've written the Markdown syntax correctly. (Note: Unfortunately as of March 2025, Cloudflare is marking that website as potentially hacked and not operational. StackEdit is a similar browser-based Markdown editor.)
Creating the Pelican Project with pelican-quickstart
In the Terminal and in the project folder, I run pelican-quickstart
. I enter the following:
Welcome to pelican-quickstart v4.11.0.
This script will help you create a new Pelican-based website.
Please answer the following questions so this script can generate the files
needed by Pelican.
> Where do you want to create your new web site? [.] .
> What will be the title of this web site? Invent with Python
> Who will be the author of this web site? Al Sweigart
> What will be the default language of this web site? [en] en
> Do you want to specify a URL prefix? e.g., https://example.com (Y/n) Y
> What is your URL prefix? (see above example; no trailing slash) https://inventwithpython.com
> Do you want to enable article pagination? (Y/n) Y
> How many articles per page do you want? [10] 10
> What is your time zone? [Europe/Rome] America/New_York
> Do you want to generate a tasks.py/Makefile to automate generation and publishing? (Y/n) Y
> Do you want to upload your website using FTP? (y/N) N
> Do you want to upload your website using SSH? (y/N) N
> Do you want to upload your website using Dropbox? (y/N) N
> Do you want to upload your website using S3? (y/N) N
> Do you want to upload your website using Rackspace Cloud Files? (y/N) N
> Do you want to upload your website using GitHub Pages? (y/N) N
Done. Your new project is available at /Users/al/gitrepos/inventwithpythondotcom
This has created the following in my project folder (the ./ means the project folder):
- ./Makefile - A makefile. I don't touch this.
- ./content - The folder where all the source files (written as .md Markdown files) for web pages and blog posts will go.
- ./output - When I run the
pelican content
command, the generated static files will be put in this folder. - ./pelicanconf.py - The configuration file for the preview website. (Explained in the next section.)
- ./publishconf.py - The configuration file for the production website. (Explained in the next section.)
- ./tasks.py - (TODO NOTE: I actually don't use this, but I should learn what it is. Seems handy for doing basic tasks related to website generation and testing?)
Pelican has the concept of pages and articles. The "Writing Content" page of the official documentation says:
Pelican considers “articles” to be chronological content, such as posts on a blog, and thus associated with a date.
The idea behind “pages” is that they are usually not temporal in nature and are used for content that does not change very often (e.g., “About” or “Contact” pages).
Both articles and pages are written as .md Markdown files in the ./content folder. We call these the source files for our Pelican website.
Pages and articles are processed when you run Pelican to generate the static files (which are put in the ./output folder as .html files) to put online on the actual website. Meanwhile, "static files" is a specific term in Pelican for things like images, downloadable .zip files, and other files that are not processed but rather just copied as-is to the ./output folder. (I'll discuss static files later.)
I'm going to start by adding pages, and then later set up a blog for the website as well for stuff like news and announcement posts.
Just to make it clear: The reason you want to use software like Pelican is that it makes it easy to handle your website as it gets bigger. If you have two hundred blog posts and want to make a change to the layout or look of the website, you don't want to have to individually edit two hundred HTML files. And when you make mistakes, your website will start accruing inconsistent links and style and just become a mess to maintain. With Pelican, you can make a small change to the Pelican theme or settings and then have Pelican regenerate the static files. Pelican (and other CMSes or content management systems makes it much easier to maintain even moderately large websites, and it's worth the time it takes to learn how to use Pelican.
Preview vs Production Website
TL;DR: You can run the dev server with pelican -rl
and go to http://localhost:8000. If you know what a dev server/production server is, you can skip this section.
When you are creating your website, you won't directly open the .html files that Pelican generates. Instead, you'll run Pelican's temporary, private, web server. This is called the local development server website or preview website. You'll be able to view this in your browser by going to http://localhost:8000. (Your browser will give you an "Unable to connect" error message if you click that link without running the web server first.)
You generate the preview website by running pelican -rl
in a Terminal window from the project folder. This causes Pelican to process all the page and article source files, then set up the "dev" web server for you to look at. (Only you can view this web server, random people on the internet cannot.) The "l" in -rl
stands for "listen", meaning Pelican runs the dev web server and listens for connections from your browser. The "r" in -rl
means Pelican will automatically regenerate the website when it detects you've changed the settings or source files. This saves you from having to restart the dev server yourself.
You can shut down the dev server by pressing Ctrl-C in the Terminal window.
You can view the preview site to check for any typos or changes you'd like to make. Once you are satisified, you can then run pelican -s publishconf.py
to generate the website using your production settings. The production website is the actual, online website that people will see when they go to your domain name. These static .html files are put into the output folder, where you can upload them to your website using one the methods I describe later.
The Default Settings in pelicanconf.py
Let's look at the default settings in the pelicanconf.py file that pelican-quickstart
created for us. Remember that the settings in pelicanconf.py are for the offline, preview version hosted on our computer before we upload it to the internet. This way we can check for any typos or mistakes before putting it on the actual online web host for the inventwithpython.com domain name.
The pelicanconf.py file has most of the setttings we want for our website. The publishconf.py will automatically copy all of these settings, overriding only a few for the production website.
All of these settings are documented on the Settings page of the official Pelican documentation.
The contents of the pelicanconf.py file look like this (based on the answers I gave to pelican-quickstart
:
AUTHOR = 'Al Sweigart'
SITENAME = 'Invent with Python'
SITEURL = ""
PATH = "content"
TIMEZONE = 'America/New_York'
DEFAULT_LANG = 'en'
# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
# Blogroll
LINKS = (
("Pelican", "https://getpelican.com/"),
("Python.org", "https://www.python.org/"),
("Jinja2", "https://palletsprojects.com/p/jinja/"),
("You can modify those links in your config file", "#"),
)
# Social widget
SOCIAL = (
("You can add links in your config file", "#"),
("Another social link", "#"),
)
DEFAULT_PAGINATION = 10
# Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True
Let's go the settings in pelicanconf.py for the preview site:
AUTHOR = 'Al Sweigart'
The AUTHOR
setting is the default author of pages and articles if they don't have a different author set in their metadata.
SITENAME = 'Invent with Python'
The SITENAME
setting text is used in the <title> tags and for link text throughout the pages and articles. Note that the setting is SITENAME
and not SITE_NAME
.
SITEURL = ""
The SITEURL
setting is the base of the website's URL. This is a blank string in the preview version but 'https://inventwithpython.com'
in publishconf.py for the production version.
PATH = "content"
The PATH
setting is where all of the source files (the page and article .md files) go in this folder. I've never had a reason to change this setting.
TIMEZONE = 'America/New_York'
The TIMEZONE
setting is the default timezone for the date and timestamps for articles for blog posts. Individual source files can havea timezone that overrides this default.
DEFAULT_LANG = 'en'
The DEFAULT_LANG
setting is what language the articles and pages are written in. The setting is a two-letter ISO language code [and you can find a list of them on Wikipedia]](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes). The 'en'
setting stands for English. Individual source files can have a setting that overrides this default.
# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
These are RSS/Atom feed settings. To make preview site generation faster, we don't create the RSS feeds (the older but more popular feed format) and Atom feeds (the newer but less popular feed format). Leave all of these as None
here in pelicanconf.py. We'll set them to something different in publishconf.py for the production website.
# Blogroll
LINKS = (
("Pelican", "https://getpelican.com/"),
("Python.org", "https://www.python.org/"),
("Jinja2", "https://palletsprojects.com/p/jinja/"),
("You can modify those links in your config file", "#"),
)
The LINKS
setting populates the links section. Your Pelican theme you choose decides where exactly these links appear, but the docs say "A list of tuples (Title, URL) for links to appear on the header."
# Social widget
SOCIAL = (
("You can add links in your config file", "#"),
("Another social link", "#"),
)
The SOCIAL
setting is similar to the LINKS
setting. The Pelican theme you choose decides where exactly these links appear, but the docs say "A list of tuples (Title, URL) to appear in the “social” section." which is probably in a sidebar.
DEFAULT_PAGINATION = 10
The DEFAULT_PAGINATION
setting is how many articles to show per page. Setting this to False
makes ALL articles listed on the index page. Too large, and the index page takes a while to load. Too small, and your readers have to click "Next Page" over and over again. 10
is a good amount.
# Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True
The RELATIVE_URLS
setting setting is whether the links in the generated .html files use relative or absolute URLs. Relative URLs look like ../pages/about.html
while non-relative URLs (i.e. absolute URLs) look like /pages/about.html
(for the developer) or https://inventwithpython.com/pages/about.html
. The question of absolute vs relative URLs is a web design decision, but I'm going to go along with the recommendation of Pelican's default value and leave RELATIVE_URLS
set to False
.
Also, setting RELATIVE_URLS
to True
would use relative URLs in the RSS and Atom feeds, and you always want absolute URLs in those feeds.
Notice later in the publishconf.py file that there is an explicit RELATIVE_URLS = False
line that would override if you set it to True
in pelicanconf.py, because you do want absolute URLs in your production website. I'll leave this line commented (and RELATIVE_URLS
at its default False
setting) because I'll use absolute URLs in the preview website as well.
The contents of the publishconf.py file look like this:
# This file is only used if you use `make publish` or
# explicitly specify it as your config file.
import os
import sys
sys.path.append(os.curdir)
from pelicanconf import *
# If your site is available via HTTPS, make sure SITEURL begins with https://
SITEURL = "https://inventwithpython.com"
RELATIVE_URLS = False
FEED_ALL_ATOM = "feeds/all.atom.xml"
CATEGORY_FEED_ATOM = "feeds/{slug}.atom.xml"
DELETE_OUTPUT_DIRECTORY = True
# Following items are often useful when publishing
# DISQUS_SITENAME = ""
# GOOGLE_ANALYTICS = ""
Note that the publishconf.py files first imports the pelicanconf.py file's settings. The publishconf.py settings for the production site can override the preview site's settings. Notice that SITEURL
in the preview is set to a blank string, but in the production site it is set to the real 'https://inventwithpython.com'
setting.
The FEED_ALL_ATOM
and CATEGORY_FEED_ATOM
settings set the URL that Atom feeds subscribe to. By default, RSS isn't set up.
The DELETE_OUTPUT_DIRECTORY
setting is set to True
, which completely wipes the output folder before generating the site. If you rename some page or article, regenerating the site doesn't delete the file with the old name from the output folder. This is fine for the preview site; nothing links to it anyway so it's just some wasted space until we generate the production site.
What Do These Settings Mean?
The different Pelican settings can be confusing. Here are some basics:
"
* The .md source content files in ./content can either be articles (blog posts displayed chronologically in the index) or pages (separate one-off web pages for things like the About and Contact pages.)
* Settings are written in the pelicanconf.py and publishconf.py files.
* Settings are always written in uppercase letters: SITENAME
, AUTHOR
, etc.
* Metadata are written at the top of articles and pages and (for .md files) look like title: How To Feed Ducks
.
* The metadata section has to start on the first line of the source content file. You can't have a blank line at the top or set metadata in the middle or bottom of the file. It will be ignored when Pelican processes the source content files.
* Both Pelican articles and pages have metadata associated with them: things like the author, date, title, tags, slug, category, etc.
* Here's a list of metadata keys: title
, date
, modified
, tags
, keywords
, category
, slug
, author
, authors
, summary
, lang
, translation
, status
, template
, save_as
, url
.
* Some settings (like AUTHOR
or DEFAULT_LANG
) will provide default metadata values so you don't have to specify them in every article and page.
* Pages require only the title
metadata. Without a title, the source content file is ignored and not processed into a .html file for the ./output folder.
* Articles require the title
and date
metadata. Without them, the source content file is ignored.
* If you add a DEFAULT_DATE
setting to pelicanconf.py, you don't have to specify the date
metadata in the source content file. Setting it to 'fs'
makes it use the file's creation time. ("fs" stands for "file system".) This makes sense: a blog post or news article would have a date, but an About or Contact page would not.
* I prefer to always specify date
metadata directly in the article file rather than set DEFAULT_DATE = 'fs'
to automatically use the file system. To me, the control of setting it directly is worth the inconvenience.
Folder Layout of the Source Content Files and Output Files
Where to put source content files for your articles and pages in Pelican (and how the settings changes this) can be a bit confusing. Here are some basics:
- I prefer to use .md files to write stuff in markdown, rather than HTML or RestructuredText. I find it easier.
- By default, all source files in ./content (except in ./content/pages) are articles, not pages. (This is ideal for a blog-article-oriented website, but my Invent with Python website will be page-oriented.)
- Remember that articles require at least a
title
anddate
metadata in them. - By default, all source files in ./content/pages) are pages.
- Remember that pages require at least a
title
metadata in them.
When you run pelican content
or pelican -s publishconf.py content
, Pelican generates the website's HTML files and places them in ./output.
If you want to change the layout of source content files in ./content, you can change some of the settings in pelicanconf.py or publishconf.py:
INDEX_SAVE_AS
is where the output file for the index of articles (i.e. blog posts) is put. By default it's'index.html'
, putting it in ./output/index.html. However, I want this file to be a home page rather than the list of blog posts, so I change it to'/blog/index.html'
so it's put in ./output/blog/index.html- The source content files (articles and pages) are all in ./content because
pelican-quickstart
set thePATH
setting to'content'
. - Pages are in ./content/pages because
PATH
is'content'
and the default setting forPAGE_PATHS
is['pages']
. - Articles are in ./content because
PATH
is'content'
and the default setting forARTICLE_PATHS
is['']
. - However, even though ./content/pages is under ./content, files in ./content/pages are never considered articles because the
ARTICLE_EXCLUDES
setting always include all the paths inPAGE_PATHS
.
The PATH
, PAGE_PATHS
, ARTICLE_PATHS
, ARTICLE_EXCLUDES
, and other settings are about where the source content files are. When Pelican processes these files to generate the .html files for the static site, it has to decide where in ./output to put the processed files. This is controlled by the URL settings:
- Pelican processes articles to ./output based on the
ARTICLE_SAVE_AS
setting, which is'{slug}.html'
by default. (More on slugs later.) - The links to articles in generated .html files is based on the
ARTICLE_URL
setting, which is'{slug}.html'
by default. - Pelican processes pages to ./output/pages based on the
PAGE_SAVE_AS
setting, which is'pages/{slug}.html'
by default. - The links to pages in generated .html files is based ont he
PAGE_URL
setting, which is'{slug}.html
by default.
A "slug" is a URL-friendly version of the title, meant to be human-readable and search-engine-friendly. For example, if an article's title is set with title: It's a Wonderful Life!
then Pelican generates the slug its-a-wonderful-life
(getting rid of punctuation and changing spaces to dashes). The ARTICLE_SAVE_AS
setting of '{slug}.html'
then uses ./output/its-a-wonderful-life.html as the generated file for the article.
The slug is based on the title because the SLUGIFY_SOURCE
setting is set to 'title'
by default. Alternatively, you can set this to 'basename'
to have the slug based on the source content filename instead.
Keep in mind that if you change the slug by changing the title (or filename), Pelican will automatically update all the internal links used in your website. But other websites that have linked to your web pages will now result in 404 errors.
I set SLUGIFY_SOURCE
to basename
and make the filename similar to the title. This way, I'm free to later make tiny changes to the title
metadata without changing the URL and breaking links from other websites.
Why are \*\_SAVE_AS
and \*\_URL
Two Different Settings?
It may seem odd that are two different settings for \*\_SAVE_AS
and \*\_URL
settings, especially because you should set them to the same value.
There is only one case (as far as I've been able to tell) where you should set them to something different: setting the \*\_SAVE_AS
to something like '{slug}/index.html'
while setting the \*\_URL
setting to '{slug}'
.
When creating web pages, index.html is the default filename when your browser accesses a fodler. So if your browser goes to https://inventwithpython.com or https://inventwithpython.com/about, it's really accessing https://inventwithpython.com/index.html and https://inventwithpython.com/about/index.html, respectively.
If you don't want the .html at the end of the URL, change your \*\_SAVE_AS
and \*\_URL
settings to follow this pattern for articles and pages.
I prefer to keep the \*\_SAVE_AS
and \*\_URL
settings identical because I don't mind having the .html extension in the URL and I want people to be able to download individual web pages that have separate names. Having a bunch of index.html files that are only different based on the folder they are in is less than ideal (in my opinion).
If you want the title metadata to automatically be set to the filename of the source content file (and skip setting it in the file itself), add FILENAME_METADATA = '(?P<title>.*)'
to pelicanconf.py. I don't do this because I don't want spaces and uppercase characters in my filenames, like This is the Title.md.
Categories
One of the metadata that you can attach to pages and articles is the category. Pages and articles can have at most one category, so I often use them to identify a series of blog posts. For example, python_tutorial_part_1.md, python_tutorial_part_2.md, and python_tutorial_part_3.md could all have category: BeginnerPythonTutorial
. Pelican generates a ./output/categories.html page that lists all the categories, and then a website visitor could click the BeginnerPythonTUtorial category to find links to every blog post in that series.
However, instead of adding category: BeginnerPythonTutorial
to every source file in that category, you could place all the source files in ./content/BeginnerPythonTutorial and Pelican will automatically consider them in the BeginnerPythonTutorial category. This is because the USE_FOLDER_AS_CATEGORY
setting has a default value of True
. You can set this to False
, in which case the category of the source file depends on its category
metadata.
I don't like using folders as categories (I prefer setting them directly in the source content file), so I set USE_FOLDER_AS_CATEGORY
to False
.
Note that only the immediate folder the source content file is in is used for its category. For example, ./content/Cats/my_article1.md and ./content/Animals/Misc/Cats/my_article2.md are both considered to be in the Cats category. The Animals/Misc folders are ignored. This can be rather confusing, so it's best to just have one level of folders in ./content if you leave USE_FOLDER_AS_CATEGORY
as True
.
Tags
Tags are topic keywords that you can use to group together topically-related articles and pages. While an article or page can be in at most one category, you can apply any number of tags. Tags are added to an article or page with the tags
metadata and separated by comma. For example, tags: python, programming
or tags: cats
. Tags are case-insensitive: tags: python
and tags: Python
are considered the same.
Pelican generates a ./output/tags/index.html file that lists all the tags in the site. This page links individual tag pages, such as ./output/tag/python.html if you have a "python" tag.
Updating pelicanconf.py and publishconf.py
Here's where a lot of my opinions on how to use Pelican come in. These changes to the settings make sense to me for the inventwithpython.com website, but you might want something different for your sites. I explain my changes and the reasoning for my changes here. Check out the Settings page of the official documentation.
Most changes go into pelicanconf.py, where I want them to apply to the preview and production website. The changes I add to publishconf.py are just needed for the production website. In general, I want to keep the preview and production websites as similar as possible because it's easier to test things offline on my computer with the preview website.
Here are the changes I'm making:
LOAD_CONTENT_CACHE = False # default was True
DELETE_OUTPUT_DIRECTORY = True # default was False
THEME = 'simple' # default was 'notmyidea''
INDEX_SAVE_AS = '/blog/index.html' # default was 'index.html'
ARTICLE_PATHS = ['blog'] # default was ['']
PAGE_PATHS = [''] # default was ['pages']
USE_FOLDER_AS_CATEGORY = False # default was True
SLUGIFY_SOURCE = 'basename' # default was 'title'
PATH_METADATA = r'(?P<path_no_ext>.*)\..*' # default was ''
ARTICLE_URL = '{path_no_ext}.html' # default was '{slug}.html'
ARTICLE_SAVE_AS = '{path_no_ext}.html' # default was '{slug}.html'
PAGE_URL = '{path_no_ext}.html' # default was 'pages/{slug}.html'
PAGE_SAVE_AS = '{path_no_ext}.html' # default was 'pages/{slug}.html'
SUMMARY_MAX_PARAGRAPHS = 1
Let's examine each of these individually.
LOAD_CONTENT_CACHE = False # default was True
The official documentation has this note:
When experimenting with different settings (especially the metadata ones) caching may interfere and the changes may not be visible. In such cases disable caching with
LOAD_CONTENT_CACHE = False
or use the--ignore-cache
command-line switch.
I follow this advice and set LOAD_CONTENT_CACHE
to False
.
DELETE_OUTPUT_DIRECTORY = True # default was False
If the DELETE_OUTPUT_DIRECTORY setting is True
, then Pelican will first delete all files in the ./output folder before generating the new static blog files. Unless you have hundred megabyte files or thousands of blog posts in your Pelican site, I would add DELETE_OUTPUT_DIRECTORY = True
to pelicanconf.py. It already exists in publishconf.py, but I've been playing around with Pelican settings enough that I encountered some issues (mostly changing settings or renaming files) that can be prevented by wiping the ./output folder before generating the website. You can take this line out of pelicanconf.py once you have the settings and theme decided.
INDEX_SAVE_AS = '/blog/index.html' # default was 'index.html'
The INDEX_SAVE_AS setting tells Pelican where to put the article index (or simply called the index in Pelican) that lists the latest articles. By default this is the home page of the website. This is fine if you want the home page to show blog posts. However, I want to create a more traditional website, and have the article/blog index under the /blog URL.
PAGE_PATHS = [''] # default was ['pages']
(TODO NOTE: The article path is automatically included in page_excludes so that the articles folder can never be used for pages.)
PATH_METADATA = r'(?P<path_no_ext>.*)\..*' # default was ''
There's a part in the URL Settings section of the Settings documentation that says:
If you don’t want that flexibility and instead prefer that your generated output paths mirror your source content’s filesystem path hierarchy, try the following settings:
PATH_METADATA = '(?P
.)..' ARTICLE_URL = ARTICLE_SAVE_AS = PAGE_URL = PAGE_SAVE_AS = '{path_no_ext}.html'
This is what I want for my site: I want to layout folders in ./content and have the layout of the website match it. Note that there's a small typo in the docs: we need to make this a raw string by adding an r
prefix since it's a regular expression with a \
backslash character. (Though I believe that since \.
is not a regex escape sequence, it technically doesn't need to be escaped.)
The PATH_METADATA section takes the named group (this is a regex term, it refers to the (?P<path_no_ext>.*)
) and adds the matching text (taken from the path of the source file) and adds it to the metadata object. What this means is that we can now use '{path_no_ext}
in our other settings and it'll be replaced by the matched text.
In this case, the matched text will be everything up to the first period.
I'll give an example with the next setting.
TODO NOTE: Finish this section.
ARTICLE_URL = '{path_no_ext}.html' # default was '{slug}.html'
The documentation for the ARTICLE_URL setting is "The URL to refer to an article." This isn't very helpful documentation. What it means is that
ARTICLE_SAVE_AS = '{path_no_ext}.html' # default was
PAGE_URL = '{path_no_ext}.html'
PAGE_SAVE_AS = '{path_no_ext}.html'
Creating Content for The Website
With the settings all configured, we can finally start creating the actual content for the website.
First, I'll create ./content/pages/index.md for the home page. I'll start it with this content:
Title:
Save_as: index.html
Status: hidden
This is the Home page.
This took a lot of experimentation on my part to figure out, so I'll try to explain it all here:
Keep in mind that this a Markdown-formatted file with a .md file extension, however you can embed HTML tags directly into it.
The Title:
metadata is left blank. The official Pelican docs say that the title metadata is the only metadata required. Without this line, Pelican will skip processing the ./content/index.md file and you won't have a home page for your site. However, most themes already put the SITENAME
setting's text ('Invent with Python'
, in my case) at the top of all web pages, so I don't want a second one appearing on the home page.
The Save_as: index.html
metadata ensures that this page is saved as ./output/index.html so it will be the home page of the website. Otherwise, this page would be created in the ./output/pages folder with a filename based on it's slug. And since we have no Slug:
metadata here, this page isn't processed at all. (Though it still gets counted in the number of pages processed in the output of pelican -rl
, oddly enough.)
The Status: hidden
for this page is so that it doesn't show up in the list of pages. A lot of Pelican themes have a sidebar or top bar that lists all the pages. Since website visitors can go to the home page by clicking the website name at the top, this would be redundant. Also, since we set the title to blank, the link for this page in the list of pages would appear as an awkward blank space.
In summary, have these three metadata entries in your index.md file in the ./content/pages folder to create a home page for your site.
The rest of the file is the markdown and HTML for the web page.
Next, I'll create ./content/pages/about.md for an About page. I'll start it with this content:
Title: About
This is the About page.
This gets processed into ./output/pages/about.html based on a lowercasing of the Title: About
metadata (and not the about.md filename).
I'm going to describe the results of my experimentation:
It must be processed under the ./output/pages folder and I haven't found a reliable way to make it processed to ./output/about.html. The title metadata text will be the text used for any links to this page. If you want the processed filename to be different, you can set a Slug:
metadata. For example, this:
Title: AboutTitle
Slug: AboutSlug
This is the About page.
This processes the page to ./output/pages/AboutSlug.html (note that the filename is not lowercased when based on the Slug:
metadata) but the link text viewable in the browser is for "AboutTitle".
I think the simplest approach is to just specify a Title:
and leave out Slug:
and just accept that the processed file ends up under ./output/pages. I want this page to appear in the list of pages, so I leave out the Status: hidden
metadata.
I create the other TODO NOTE: Finish this section.
Images and Other Downloadable Static Files
TODO NOTE: Finish this section.