diff --git a/docs/templates.md b/docs/templates.md
index 39f989d2e..749c7a8c2 100644
--- a/docs/templates.md
+++ b/docs/templates.md
@@ -1,4 +1,4 @@
-Starlette is not *strictly* coupled to any particular templating engine, but
+Starlette is not _strictly_ coupled to any particular templating engine, but
Jinja2 provides an excellent choice.
Starlette provides a simple way to get `jinja2` configured. This is probably
@@ -33,7 +33,7 @@ so we can correctly hyperlink to other pages within the application.
For example, we can link to static files from within our HTML templates:
```html
-
+
```
If you want to use [custom filters][jinja2], you will need to update the `env`
@@ -50,6 +50,42 @@ templates = Jinja2Templates(directory='templates')
templates.env.filters['marked'] = marked_filter
```
+## Context processors
+
+A context processor is a function that returns a dictionary to be merged into a template context.
+Every function takes only one argument `request` and must return a dictionary to add to the context.
+
+A common use case of template processors is to extend the template context with shared variables.
+
+```python
+import typing
+from starlette.requests import Request
+
+def app_context(request: Request) -> typing.Dict[str, typing.Any]:
+ return {'app': request.app}
+```
+
+### Registering context templates
+
+Pass context processors to `context_processors` argument of the `Jinja2Templates` class.
+
+```python
+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]
+)
+```
+
+!!! info
+ Asynchronous functions as context processors are not supported.
+
## Testing template responses
When using the test client, template responses include `.template` and `.context`
diff --git a/starlette/templating.py b/starlette/templating.py
index a36c264ed..da340f569 100644
--- a/starlette/templating.py
+++ b/starlette/templating.py
@@ -2,6 +2,7 @@
from os import PathLike
from starlette.background import BackgroundTask
+from starlette.requests import Request
from starlette.responses import Response
from starlette.types import Receive, Scope, Send
@@ -59,10 +60,16 @@ class Jinja2Templates:
"""
def __init__(
- self, directory: typing.Union[str, PathLike], **env_options: typing.Any
+ self,
+ directory: typing.Union[str, PathLike],
+ context_processors: typing.Optional[
+ typing.List[typing.Callable[[Request], typing.Dict[str, typing.Any]]]
+ ] = None,
+ **env_options: typing.Any
) -> None:
assert jinja2 is not None, "jinja2 must be installed to use Jinja2Templates"
self.env = self._create_env(directory, **env_options)
+ self.context_processors = context_processors or []
def _create_env(
self, directory: typing.Union[str, PathLike], **env_options: typing.Any
@@ -94,6 +101,11 @@ def TemplateResponse(
) -> _TemplateResponse:
if "request" not in context:
raise ValueError('context must include a "request" key')
+
+ request = typing.cast(Request, context["request"])
+ for context_processor in self.context_processors:
+ context.update(context_processor(request))
+
template = self.get_template(name)
return _TemplateResponse(
template,
diff --git a/tests/test_templates.py b/tests/test_templates.py
index ad42488de..0bf4bce07 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -32,3 +32,31 @@ def test_template_response_requires_request(tmpdir):
templates = Jinja2Templates(str(tmpdir))
with pytest.raises(ValueError):
templates.TemplateResponse("", {})
+
+
+def test_calls_context_processors(tmp_path, test_client_factory):
+ path = tmp_path / "index.html"
+ path.write_text("Hello {{ username }}")
+
+ async def homepage(request):
+ return templates.TemplateResponse("index.html", {"request": request})
+
+ def hello_world_processor(request):
+ return {"username": "World"}
+
+ app = Starlette(
+ debug=True,
+ routes=[Route("/", endpoint=homepage)],
+ )
+ templates = Jinja2Templates(
+ directory=tmp_path,
+ context_processors=[
+ hello_world_processor,
+ ],
+ )
+
+ client = test_client_factory(app)
+ response = client.get("/")
+ assert response.text == "Hello World"
+ assert response.template.name == "index.html"
+ assert set(response.context.keys()) == {"request", "username"}