plain.pages
Serve static pages, markdown, and assets from templates/pages directories.
Overview
The plain.pages package automatically discovers and serves static pages from templates/pages directories in your app and installed packages. Pages can be HTML, Markdown, redirects, or static assets, with support for frontmatter variables and template rendering.
# app/templates/pages/about.md
---
title: About Us
---
# About Our Company
We build great software.
This creates a page at /about/ that renders the markdown content with the title "About Us".
Pages are discovered from:
{package}/templates/pages/for each installed packageapp/templates/pages/in your main application
The file path determines the URL:
index.htmlorindex.md→/about.htmlorabout.md→/about/docs/getting-started.md→/docs/getting-started/styles.css→/styles.css(served as static asset)
Page types
HTML pages
HTML files are rendered as templates with access to the standard template context:
<!-- app/templates/pages/features.html -->
---
title: Features
---
<h1>{{ page.title }}</h1>
<p>Current user: {{ get_current_user() }}</p>
Markdown pages
Markdown files (.md) are automatically converted to HTML:
<!-- app/templates/pages/guide.md -->
---
title: User Guide
template_name: custom-page.html
---
# User Guide
This is **markdown** content with [links](/other-page/).
Redirect pages
Files with .redirect extension create redirects:
# app/templates/pages/old-url.redirect
---
url: /new-url/
temporary: false
---
Assets
Any file that isn't HTML, Markdown, or a redirect is served as a static asset:
app/templates/pages/
├── favicon.ico
├── robots.txt
├── images/
│ └── logo.png
└── docs/
└── guide.pdf
These are served at their exact paths: /favicon.ico, /images/logo.png, etc.
Template pages
Files containing .template. in their name are skipped and not served as pages. Use these for shared template fragments:
app/templates/pages/
├── base.template.html # Not served
└── index.html # Served at /
Serving raw markdown
You can optionally serve raw markdown content (without frontmatter) alongside rendered HTML pages. When enabled, markdown pages can be accessed as raw markdown via:
- Accept header negotiation - Send
Accept: text/markdownorAccept: text/plainto get raw markdown - Separate .md URLs - Access
/docs/guide.mdalongside/docs/guide/
# settings.py
PAGES_SERVE_MARKDOWN = True
With this setting enabled:
/docs/guide/withAccept: text/html→ Rendered HTML page/docs/guide/withAccept: text/markdown→ Raw markdown content/docs/guide.md→ Raw markdown content (without frontmatter)
The raw markdown serves with text/plain content type, making it useful for:
- External markdown processors
- API consumers needing markdown source
- Documentation tools that need raw content
- Command-line tools like curl or httpie
Note: This feature is disabled by default. Only enable it if you need to serve raw markdown content.
Linking to markdown URLs
When markdown serving is enabled, you can link to the raw markdown version from templates:
<!-- In your page template -->
<a href="{{ page.get_markdown_url() }}">View Source</a>
<a href="{{ page.get_markdown_url() }}">Download Markdown</a>
The get_markdown_url() method returns:
- The markdown URL (e.g.,
/docs/guide.md) if the page is markdown and the feature is enabled Noneif the page is not markdown or the feature is disabled
Frontmatter
Pages support YAML frontmatter for configuration:
---
title: Custom Title
template_name: my-template.html
render_plain: true
custom_var: value
---
Available frontmatter options:
title: Page title (defaults to filename)template_name: Custom template to userender_plain: Skip template rendering (for markdown)url: Redirect URL (for .redirect files)temporary: Redirect type (for .redirect files)- Any custom variables accessible via
page.vars
Custom views
You can extend the view classes to customize page rendering:
from plain.pages.views import PageView
class CustomPageView(PageView):
def get_template_context(self):
context = super().get_template_context()
context["extra_data"] = self.get_extra_data()
return context
The main view classes are:
PageView: Renders HTML and Markdown pagesPageRedirectView: Handles redirectsPageAssetView: Serves static assetsPageMarkdownView: Serves raw markdown content
FAQs
How do I use a custom base template for markdown pages?
Set the template_name in frontmatter to specify your own template. Your template should include {{ page.content }} to render the markdown content.
Can I use template tags and filters in markdown files?
Yes. Unless you set render_plain: true in the frontmatter, markdown files are processed as templates first, then converted to HTML. You can use any template tags and filters available in your project.
How do I access frontmatter variables in templates?
Custom frontmatter variables are available through page.vars. For example, if your frontmatter includes author: Jane Doe, you can access it with {{ page.vars.author }}.
Why isn't my page showing up?
Check that your file is in a templates/pages/ directory and doesn't contain .template. in the filename. Template files are intentionally skipped.
Installation
Install the plain.pages package from PyPI:
uv add plain.pages