Skip to content

Templates

Starlette is not strictly coupled to any particular template engine, but Jinja2 is an excellent choice.

Jinja2 templates

Signed by: Jinja2Templates(directory, context_processors=None, **env_options)

  • directory - A string, os.Pathlike, or a string or list of os.Pathlike, representing a directory path.
  • context_processors - A list of functions that returns a dictionary to add to the template context.
  • **env_options - Additional keyword parameters passed to the Jinja2 environment.

Starlette provides an easy way to configure jinja2. This is probably what you want to use by default.

from starlette.applications import Starlette
from starlette.routing import Route, Mount
from starlette.templating import Jinja2Templates
from starlette.staticfiles import StaticFiles


templates = Jinja2Templates(directory='templates')

async def homepage(request):
    return templates.TemplateResponse(request, 'index.html')

routes = [
    Route('/', endpoint=homepage),
    Mount('/static', StaticFiles(directory='static'), name='static')
]

app = Starlette(debug=True, routes=routes)

Note that the incoming request instance must be included as part of the template context.

The Jinja2 template context will automatically include an url_for function, so we can correctly hyperlink to other pages within the application.

For example, we can link from within our HTML template to static files:

<link href="{{ url_for('static', path='/css/bootstrap.min.css') }}" rel="stylesheet" />

If you want to use custom filters, you will need to update env properties of Jinja2Templates:

from commonmark import commonmark
from starlette.templating import Jinja2Templates

def marked_filter(text):
    return commonmark(text)

templates = Jinja2Templates(directory='templates')
templates.env.filters['marked'] = marked_filter

Using custom jinja2.Environment instances

Starlette also accepts a preconfigured jinja2.Environment instance.

import jinja2
from starlette.templating import Jinja2Templates

env = jinja2.Environment(...)
templates = Jinja2Templates(env=env)

context handler

A context handler is a function that returns a dictionary to merge into the template context. Each function takes only one argument request and must return a dictionary to add to the context.

A common use case for template handlers is to extend template contexts with shared variables.

import typing
from starlette.requests import Request

def app_context(request: Request) -> typing.Dict[str, typing.Any]:
    return {'app': request.app}

Register Context Template

Pass the context handler to the context_processors parameter of the Jinja2Templates class.

import typing

from starlette.requests import Request
from starlette.templating import Jinja2Templates

def app_context(request: Request) -> typing.Dict[str, typing.Any]:
    return {'app': request.app}

templates = Jinja2Templates(
    directory='templates', context_processors=[app_context]
)

!!! Information is not supported as an asynchronous function of the context handler.

Test Template Response

When using the test client, the template response includes the .template and .context attributes.

from starlette.testclient import TestClient


def test_homepage():
    client = TestClient(app)
    response = client.get("/")
    assert response.status_code == 200
    assert response.template.name == 'index.html'
    assert "request" in response.context

Custom Jinja2 Environment

Jinja2Templates accepts all options supported by Jinja2 Environment. This will allow more control over Environment instances created by Starlette.

For the list of options available for Environment, you can view the Jinja2 documentation here

from starlette.templating import Jinja2Templates


templates = Jinja2Templates(directory='templates', autoescape=False, auto_reload=True)

Asynchronous template rendering

Jinja2 supports asynchronous template rendering, however, in general we recommend that you avoid having your templates contain logic that calls database lookups or other I/O operations.

Instead, we recommend that you ensure that your endpoints perform all I/O, for example, rigorously evaluate any database queries within the view and include the final results in the context.