Skip to content

Custom filters

Filters are usually implemented as simple Python functions. When rendered, Liquid will find the function in Environment.filters, then call it, passing the input value as the first argument, followed by positional and keyword arguments given by the template author. The function's return value then becomes the result of the filtered expression.

Filters can actually be any Python callable. Implementing a filter as a class with a __call__ method or as a closure can be useful if you want to configure the filter before registering it with an Environment.

Also see the filter helpers API documentation.

Tip

See liquid2/builtin/filters for lots of examples.

Add a filter

To add a filter, add an item to Environment.filters. It's a regular dictionary mapping filter names to callables.

In this example we add ends_with, a filter that delegates to Python's str.endswith. The @string_filter decorator coerces the input value to a string, if it is not one already.

from liquid2 import Environment
from liquid2.filter import string_filter


@string_filter
def ends_with(left: str, val: str) -> bool:
    return left.endswith(val)


env = Environment()
env.filters["ends_with"] = ends_with

source = """\
{% assign foo = "foobar" | ends_with: "bar" %}
{% if foo %}
    do something
{% endif %}"""

template = env.from_string(source)
print(template.render())

With context

Sometimes a filter will need access to the current render context. Use the @with_context decorator to have an instance of RenderContext passed to your filter callable as a keyword argument named context.

Here we use the render context to resolve a variable called "handle".

from liquid2 import Environment
from liquid2.filter import string_filter
from liquid2.filter import with_context


@string_filter
@with_context
def link_to_tag(label, tag, *, context):
    handle = context.resolve("handle", default="")
    return (
        f'<a title="Show tag {tag}" href="/collections/{handle}/{tag}">{label}</a>'
    )

class MyEnvironment(Environment):
    def register_tags_and_filters(self):
        super().register_tags_and_filters()
        self.filters["link_to_tag"] = link_to_tag

env = MyEnvironment()
# ...

With environment

Use the @with_environment decorator to have the current Environment passed to your filter callable as a keyword argument named environment.

import re

from markupsafe import Markup
from markupsafe import escape as markupsafe_escape

from liquid2 import Environment
from liquid2.filter import string_filter
from liquid2.filter import with_environment

RE_LINETERM = re.compile(r"\r?\n")


@with_environment
@string_filter
def strip_newlines(val: str, *, environment: Environment) -> str:
    if environment.auto_escape:
        val = markupsafe_escape(val)
        return Markup(RE_LINETERM.sub("", val))
    return RE_LINETERM.sub("", val)

# ...

Replace a filter

To replace a default filter implementation with your own, simply update the filters dictionary on your environment.

Here we replace the default slice filter with one which uses start and stop values instead of start and length, and is a bit more forgiving in terms of allowed inputs.

from liquid2 import Environment
from liquid2.filter import int_arg
from liquid2.filter import sequence_filter

@sequence_filter
def myslice(val, start, stop=None):
    start = int_arg(start)

    if stop is None:
        return val[start]

    stop = int_arg(stop)
    return val[start:stop]


env = Environment()
env.filters["slice"] = myslice
# ...

Remove a filter

Remove a built-in filter by deleting it from your environment's filters dictionary.

from liquid import Environment

env = Environment()
del env.filters["safe"]

# ...

Tip

You can add, remove and replace filters on liquid2.DEFAULT_ENVIRONMENT too. Convenience functions parse() and render() use DEFAULT_ENVIRONMENT