PAX Loader
The PAXLoader is a custom Jinja2 loader that extends the standard FileSystemLoader. It adds three features:
- Template discovery across multiple folders
- Extension fallback between
.paxand.html - Slice syntax rewriting for backwards compatibility
This page explains how it works and why it exists.
Why PAXLoader Exists
Standard Jinja2 requires exact file paths. If you write {% extends "base" %}, Jinja2 looks for templates/base.html — and fails if the file is named base.pax.
PAXLoader fixes this by searching multiple locations and extensions automatically.
It also rewrites Python-style slice syntax (posts[:3]) into a Jinja2 filter (posts | limit(3)), so templates written for other engines keep working.
Search Order
When a template is requested, PAXLoader searches in this order:
templates/<name>.paxtemplates/<name>.htmltemplates/layouts/<name>.paxtemplates/layouts/<name>.htmltemplates/partials/<name>.paxtemplates/partials/<name>.html
If none of these exist, it recursively searches templates/ for a file with the same basename.
Extension Fallback
If you request a template without an extension:
{% extends "base" %}
PAXLoader tries:
templates/base.paxtemplates/base.htmltemplates/layouts/base.paxtemplates/layouts/base.html
If you request with an extension:
{% extends "base.pax" %}
PAXLoader tries:
templates/base.paxtemplates/base.html(stem fallback)
Either way, the loader finds the template.
Slice Syntax Rewriting
PAXLoader rewrites Python-style slicing into Jinja2 filters before the template is rendered.
| Original | Rewritten |
|---|---|
{{ posts[:3] }} | {{ posts | limit(3) }} |
{{ posts[1:3] }} | {{ posts | slice(1, 3) }} |
{{ posts[:5] }} | {{ posts | limit(5) }} |
This lets templates written for a Liquid-style or Python-style engine keep working without changes.
What It Does Not Rewrite
- Negative indices:
posts[-3:] - Step syntax:
posts[::2] - Multi-dimensional:
posts[0][1]
Use the filter syntax directly for these.
How It Works Internally
The loader overrides get_source — the method Jinja2 calls to load a template.
Here is the simplified flow:
get_source(env, template)
│
▼
Try exact match
│
▼
Try extension variants (.pax, .html)
│
▼
Try folder variants (layouts/, partials/)
│
▼
Recursive basename search
│
▼
Rewrite slice syntax
│
▼
Return source
If nothing matches, it raises TemplateNotFound.
Practical Examples
Example 1 — Simple Layout
Page template about.pax:
{% extends "layouts/base" %}
{% block content %}
<h1>{{ page.title }}</h1>
{{ content | safe }}
{% endblock %}
Loader finds templates/layouts/base.pax (or .html).
Example 2 — Partial Include
Layout base.pax:
<body>
{% include "partials/nav" %}
<main>{% block content %}{% endblock %}</main>
{% include "partials/footer" %}
</body>
Loader finds templates/partials/nav.pax and templates/partials/footer.pax.
Example 3 — Slice Syntax
Template with slice syntax:
{% for post in posts[:3] %}
<h2>{{ post.title }}</h2>
{% endfor %}
Loader rewrites to:
{% for post in posts | limit(3) %}
<h2>{{ post.title }}</h2>
{% endfor %}
When to Use .pax vs .html
Functionally, they are identical. The convention:
| Use | For |
|---|---|
.pax | Templates that rely on slice syntax |
.html | Everything else |
If you are not sure, use .html.
When to Avoid PAXLoader
If you are building a template for a different Jinja2 project, use standard FileSystemLoader — PAXLoader is Pycora-specific.
Debugging Template Loading
If a template is not found:
- Check that the file exists in
templates/ - Check the spelling
- Check the file extension
- Run
python ssg.pyand look for the error message - The error shows the exact template name that failed
Best Practices
- Use
layouts/for full page templates - Use
partials/for fragments - Prefer explicit paths —
layouts/baseoverbase - Use
.htmlunless you need slice syntax - Keep the folder structure flat — do not nest deeper than
layouts/orpartials/
Troubleshooting
TemplateNotFound — file exists but not found
- Check that the file is inside
templates/ - Check that it is not in a subfolder other than
layouts/orpartials/ - Check the file extension —
.paxor.html
Wrong template is loaded
If two templates share a name (e.g., base.pax and base.html), PAXLoader prefers .pax. Rename one to avoid confusion.
Slice syntax does not work
- Check the template uses
.paxextension - Or convert to
| limit(n)manually
Custom filter not found
Check that the filter is registered in ssg.py on self.env.filters.