Plain is headed towards 1.0! Subscribe for development updates →

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 package
  • app/templates/pages/ in your main application

The file path determines the URL:

  • index.html or index.md/
  • about.html or about.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: {{ request.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 /

Markdown URLs

Optionally serve raw markdown content alongside rendered HTML pages. When enabled, each markdown file gets two URLs:

# settings.py
PAGES_MARKDOWN_URLS = True

With this setting enabled:

  • /docs/guide/ → Rendered HTML page
  • /docs/guide.md → Raw markdown content (without frontmatter)

The raw markdown URLs serve the content with text/plain content type, making them useful for:

  • External markdown processors
  • API consumers needing markdown source
  • Documentation tools that need raw content

Note: This feature is disabled by default. Only enable it if you need to serve raw markdown content.

Linking to Markdown URLs

When markdown URLs are 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
  • None if 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 use
  • render_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

The package provides view classes you can extend:

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:

Installation

Install the plain.pages package from PyPI:

uv add plain.pages