Open-Source Wikis

/

Ansible

/

Plugins

/

Filter plugins

ansible/ansible

Filter plugins

Filter plugins implement Jinja's {{ x | filter }} syntax. ansible-core ships a large library of filters under lib/ansible/plugins/filter/, plus collections that add hundreds more.

Where they live

lib/ansible/plugins/filter/
├── __init__.py
├── core.py         # Big bag of Python-only filters (now mostly moved to per-filter files)
├── encryption.py   # vault, password_hash, hashlib helpers
├── mathstuff.py    # zip, intersect, difference, log, root, ...
├── urlsplit.py
├── urls.py
├── *.yml           # Sidecar YAML docs for each filter

In modern ansible-core most filters are declared as sidecar YAML files next to the implementation. b64encode.yml, basename.yml, bool.yml, combine.yml, to_json.yml, etc. are pure-data definitions that point at a Python implementation in core.py (or wherever).

The full list is long — b64decode, b64encode, basename, bool, checksum, combinations, combine, comment, commonpath, dict2items, dirname, expandvars, extract, fileglob, flatten, from_json, from_yaml, from_yaml_all, groupby, hash, intersect, items2dict, join, log, mandatory, password_hash, path_join, permutations, quote, random, regex_findall, regex_replace, regex_search, realpath, relpath, splitext, subelements, symmetric_difference, ternary, to_datetime, to_json, to_nice_json, to_nice_yaml, to_uuid, to_yaml, type_debug, union, unique, urldecode, urlencode, urlsplit, vault, version, zip, zip_longest, etc.

How a filter gets bound into Jinja

  1. The plugin loader's filter_loader walks each filter source path.
  2. For each Python file with a FilterModule class, it reads the filters() method (returns a dict of name → callable).
  3. For each YAML sidecar, it reads the name: field plus a Python-callable reference.
  4. The lib/ansible/_internal/_templating/_jinja_plugins.py shim wraps each callable so undefined-handling, Marker propagation, and tag preservation work correctly.
  5. The wrapped callable is bound into the AnsibleEnvironment's filters dict.

A filter is then accessible as {{ x | name }} and {{ x | namespace.collection.name }}.

Writing a filter

Inline (legacy) form:

class FilterModule:
    def filters(self):
        return {
            'shouty': self.shouty,
        }

    def shouty(self, value):
        return str(value).upper() + '!'

Sidecar (modern) form: place the implementation in lib/ansible/plugins/filter/core.py:shouty and a sidecar shouty.yml next to the filter file with documentation:

---
name: shouty
short_description: Convert to uppercase with an exclamation mark
description: Calls upper() on the string and appends '!'.
positional: _input
options:
  _input:
    description: The string to shout.
    type: str
    required: true
version_added: '2.16'
author:
  - Ansible Project

The sidecar is what ansible-doc -t filter shouty renders.

Common pitfalls

  • Filters get unrendered values. A user's {{ undefined | default('x') }} passes a Marker (Ansible's hardened Undefined) into the filter. If you call regular Python on it, you'll get a runtime error. Most filters should use if value is undefined or isinstance(value, Marker): return ... early.
  • Filters should be pure functions. They get re-evaluated on every templating pass. Don't read files, hit the network, or print. Use a lookup for that.
  • Type handling. Ansible's lazy containers (_AnsibleLazyTemplateMixin) wrap dicts/lists. Filters that iterate through them are fine; filters that do isinstance(value, dict) should be careful — use isinstance(value, Mapping) from collections.abc.

Filters that are really plugins of another type

A few filters are convenience-named lookups:

  • vault (encrypt/decrypt) — wraps the vault library.
  • password_hash — wraps passlib.
  • to_uuid — wraps uuid.uuid5.

Most filters, though, are pure-Python data transforms: to_json, combine, flatten, etc.

Test plugins, briefly

{{ x is foo }} syntax is implemented by test plugins under lib/ansible/plugins/test/. They're closely related to filters but answer a boolean question rather than transforming a value. See Test plugins.

Integration points

  • Imported by: lib/ansible/_internal/_templating/_jinja_plugins.py for binding into Jinja.
  • Loaded by: filter_loader in lib/ansible/plugins/loader.py.

Entry points for modification

  • A new filter — write a plugin in a collection. Subclass nothing; provide a FilterModule class with a filters() method, or use the sidecar form.
  • Fixing a built-in filter — find it in lib/ansible/plugins/filter/. Most live in core.py or a per-filter Python file.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Filter plugins – Ansible wiki | Factory