Starlette-Templates
Starlette-Templates serves a folder of pages as a Starlette app. You give it a Jinja2 loader. It serves assets unchanged, renders templates on each request, and turns Markdown into HTML.
The package has a small set of parts:
- Static files — an ASGI app that serves the directories behind a Jinja2 loader.
- Named SQL queries — a page declares a query and runs it through a query runner you supply.
- Shortcodes — each template file in a folder becomes a Jinja tag.
- HTMX fragments — each template in
fragments/re-renders itself when a trigger fires. - HTML in Python — build elements and documents with
ht,Element, andDocument. - Error handling — raise
AppExceptionto get an HTML page or a JSON:API document.
Each part is a folder convention, not a registry. You add a page, a shortcode, or a fragment by adding a file.
Why use Starlette-Templates
Starlette gives you the ASGI building blocks. Serving a folder of pages still takes a lot of glue. This package is that glue. The main features:
- One loader — a
FileSystemLoader, aPackageLoader, or aChoiceLoaderchain. AChoiceLoaderlets an application file override a framework file. - Templates and Markdown — a
.jinjafile renders on each request. Theinclude_markdown()helper pulls in a Markdown file, and a{% markdown %}block holds Markdown written in the template. Both accept Jinja. - Template globals — every template gets
request,url_for,jsonify, andinclude_markdown. You can add your own globals and filters. - HTTP caching — ETag and Last-Modified headers,
304 Not Modifiedresponses, andCache-Controlyou set. - SQL in the page — a page declares a named query. Nothing runs until the page calls
fetch(). The query string supplies the placeholders, bound out of band. - One response, several updates — an htmx handler says what happened. Every stale fragment renders and rides back in the same response.
- Strict at start — a bad name or a collision raises
ValueErrorwhen you build the app, not on a request.
Installation
pip install starlette-templates
Hello world example
from jinja2 import FileSystemLoader
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette_templates import StaticFiles
app = Starlette(
routes=[
Mount("/", StaticFiles(loader=FileSystemLoader("site"), html=True), name="site"),
]
)
Create site/index.html.jinja:
<!DOCTYPE html>
<html>
<head><title>My App</title></head>
<body><h1>Welcome to {{ request.url.hostname }}</h1></body>
</html>
Run the app:
uvicorn app:app --reload
Open http://localhost:8000/ and StaticFiles renders the template. It serves the plain files in the same directory —
CSS, JavaScript, images — unchanged, with caching headers.
Choose your path
| If you want to | Read this |
|---|---|
| Serve assets, templates, and Markdown | Static files |
| Run a query from a page | Named SQL queries |
| Turn a folder of snippets into Jinja tags | Shortcodes |
| Update several parts of a page from one response | HTMX fragments |
| Build HTML without a template file | HTML in Python |
| Return an error as a page or as JSON | Error handling |
| Look up a class or a function | API reference |
LLM context
This documentation is available as an LLM-friendly Markdown file:
- llms.txt — links to the documentation pages
- llms-full.txt — the full documentation in one file