Skip to content

support call template_filter without parens #5736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Unreleased

- Drop support for Python 3.9. :pr:`5730`
- Remove previously deprecated code: ``__version__``. :pr:`5648`
- ``template_filter``, ``template_test``, and ``template_global`` decorators
can be used without parentheses. :issue:`5729`


Version 3.1.1
Expand Down
60 changes: 43 additions & 17 deletions docs/templating.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,31 +137,57 @@ using in this block.

.. _registering-filters:

Registering Filters
-------------------
Registering Filters, Tests, and Globals
---------------------------------------

If you want to register your own filters in Jinja2 you have two ways to do
that. You can either put them by hand into the
:attr:`~flask.Flask.jinja_env` of the application or use the
:meth:`~flask.Flask.template_filter` decorator.
The Flask app and blueprints provide decorators and methods to register your own
filters, tests, and global functions for use in Jinja templates. They all follow
the same pattern, so the following examples only discuss filters.

The two following examples work the same and both reverse an object::
Decorate a function with :meth:`~.Flask.template_filter` to register it as a
template filter.

@app.template_filter('reverse')
.. code-block:: python

@app.template_filter
def reverse(s):
return reversed(s)

.. code-block:: jinja

{% for item in data | reverse %}
{% endfor %}

By default it will use the name of the function as the name of the filter, but
that can be changed by passing a name to the decorator.

.. code-block:: python

@app.template_filter("reverse")
def reverse_filter(s):
return s[::-1]
return reversed(s)

A filter can be registered separately using :meth:`~.Flask.add_template_filter`.
The name is optional and will use the function name if not given.

.. code-block:: python

def reverse_filter(s):
return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter
return reversed(s)

In case of the decorator the argument is optional if you want to use the
function name as name of the filter. Once registered, you can use the filter
in your templates in the same way as Jinja2's builtin filters, for example if
you have a Python list in context called `mylist`::
app.add_template_filter(reverse_filter, "reverse")

{% for x in mylist | reverse %}
{% endfor %}
For template tests, use the :meth:`~.Flask.template_test` decorator or
:meth:`~.Flask.add_template_test` method. For template global functions, use the
:meth:`~.Flask.template_global` decorator or :meth:`~.Flask.add_template_global`
method.

The same methods also exist on :class:`.Blueprint`, prefixed with ``app_`` to
indicate that the registered functions will be avaialble to all templates, not
only when rendering from within the blueprint.

The Jinja environment is also available as :attr:`~.Flask.jinja_env`. It may be
modified directly, as you would when using Jinja outside Flask.


Context Processors
Expand Down
146 changes: 97 additions & 49 deletions src/flask/sansio/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,21 +660,34 @@ def add_url_rule(
)
self.view_functions[endpoint] = view_func

@setupmethod
@t.overload
def template_filter(self, name: T_template_filter) -> T_template_filter: ...
@t.overload
def template_filter(
self, name: str | None = None
) -> t.Callable[[T_template_filter], T_template_filter]:
"""A decorator that is used to register custom template filter.
You can specify a name for the filter, otherwise the function
name will be used. Example::
) -> t.Callable[[T_template_filter], T_template_filter]: ...
@setupmethod
def template_filter(
self, name: T_template_filter | str | None = None
) -> T_template_filter | t.Callable[[T_template_filter], T_template_filter]:
"""Decorate a function to register it as a custom Jinja filter. The name
is optional. The decorator may be used without parentheses.

.. code-block:: python

@app.template_filter("reverse")
def reverse_filter(s):
return reversed(s)

@app.template_filter()
def reverse(s):
return s[::-1]
The :meth:`add_template_filter` method may be used to register a
function later rather than decorating.

:param name: the optional name of the filter, otherwise the
function name will be used.
:param name: The name to register the filter as. If not given, uses the
function's name.
"""
if callable(name):
self.add_template_filter(name)
return name

def decorator(f: T_template_filter) -> T_template_filter:
self.add_template_filter(f, name=name)
Expand All @@ -686,36 +699,52 @@ def decorator(f: T_template_filter) -> T_template_filter:
def add_template_filter(
self, f: ft.TemplateFilterCallable, name: str | None = None
) -> None:
"""Register a custom template filter. Works exactly like the
:meth:`template_filter` decorator.
"""Register a function to use as a custom Jinja filter.

:param name: the optional name of the filter, otherwise the
function name will be used.
The :meth:`template_filter` decorator can be used to register a function
by decorating instead.

:param f: The function to register.
:param name: The name to register the filter as. If not given, uses the
function's name.
"""
self.jinja_env.filters[name or f.__name__] = f

@setupmethod
@t.overload
def template_test(self, name: T_template_test) -> T_template_test: ...
@t.overload
def template_test(
self, name: str | None = None
) -> t.Callable[[T_template_test], T_template_test]:
"""A decorator that is used to register custom template test.
You can specify a name for the test, otherwise the function
name will be used. Example::

@app.template_test()
def is_prime(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
) -> t.Callable[[T_template_test], T_template_test]: ...
@setupmethod
def template_test(
self, name: T_template_test | str | None = None
) -> T_template_test | t.Callable[[T_template_test], T_template_test]:
"""Decorate a function to register it as a custom Jinja test. The name
is optional. The decorator may be used without parentheses.

.. code-block:: python

@app.template_test("prime")
def is_prime_test(n):
if n == 2:
return True
for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
if n % i == 0:
return False
return True

.. versionadded:: 0.10
The :meth:`add_template_test` method may be used to register a function
later rather than decorating.

:param name: The name to register the filter as. If not given, uses the
function's name.

:param name: the optional name of the test, otherwise the
function name will be used.
.. versionadded:: 0.10
"""
if callable(name):
self.add_template_test(name)
return name # type: ignore[return-value]

def decorator(f: T_template_test) -> T_template_test:
self.add_template_test(f, name=name)
Expand All @@ -727,33 +756,49 @@ def decorator(f: T_template_test) -> T_template_test:
def add_template_test(
self, f: ft.TemplateTestCallable, name: str | None = None
) -> None:
"""Register a custom template test. Works exactly like the
:meth:`template_test` decorator.
"""Register a function to use as a custom Jinja test.

.. versionadded:: 0.10
The :meth:`template_test` decorator can be used to register a function
by decorating instead.

:param name: the optional name of the test, otherwise the
function name will be used.
:param f: The function to register.
:param name: The name to register the test as. If not given, uses the
function's name.

.. versionadded:: 0.10
"""
self.jinja_env.tests[name or f.__name__] = f

@setupmethod
@t.overload
def template_global(self, name: T_template_global) -> T_template_global: ...
@t.overload
def template_global(
self, name: str | None = None
) -> t.Callable[[T_template_global], T_template_global]:
"""A decorator that is used to register a custom template global function.
You can specify a name for the global function, otherwise the function
name will be used. Example::
) -> t.Callable[[T_template_global], T_template_global]: ...
@setupmethod
def template_global(
self, name: T_template_global | str | None = None
) -> T_template_global | t.Callable[[T_template_global], T_template_global]:
"""Decorate a function to register it as a custom Jinja global. The name
is optional. The decorator may be used without parentheses.

@app.template_global()
.. code-block:: python

@app.template_global
def double(n):
return 2 * n

.. versionadded:: 0.10
The :meth:`add_template_global` method may be used to register a
function later rather than decorating.

:param name: the optional name of the global function, otherwise the
function name will be used.
:param name: The name to register the global as. If not given, uses the
function's name.

.. versionadded:: 0.10
"""
if callable(name):
self.add_template_global(name)
return name

def decorator(f: T_template_global) -> T_template_global:
self.add_template_global(f, name=name)
Expand All @@ -765,13 +810,16 @@ def decorator(f: T_template_global) -> T_template_global:
def add_template_global(
self, f: ft.TemplateGlobalCallable, name: str | None = None
) -> None:
"""Register a custom template global function. Works exactly like the
:meth:`template_global` decorator.
"""Register a function to use as a custom Jinja global.

.. versionadded:: 0.10
The :meth:`template_global` decorator can be used to register a function
by decorating instead.

:param f: The function to register.
:param name: The name to register the global as. If not given, uses the
function's name.

:param name: the optional name of the global function, otherwise the
function name will be used.
.. versionadded:: 0.10
"""
self.jinja_env.globals[name or f.__name__] = f

Expand Down
Loading
Loading