Introduction

Ansible filters transform data within {{ }} expressions — set defaults, convert types, manipulate strings, filter lists, parse JSON, and more. They're Jinja2 filters extended with Ansible-specific additions.

Default Values

# Set default if undefined
- debug:
    msg: "{{ my_var | default('fallback') }}"

# Default for empty strings too
- debug:
    msg: "{{ my_var | default('fallback', true) }}"

# Chained defaults
- debug:
    msg: "{{ primary | default(secondary) | default('last resort') }}"

# Mandatory (fail if undefined)
- debug:
    msg: "{{ required_var | mandatory }}"

Type Conversion

# Convert types
port: "{{ input_port | int }}"
enabled: "{{ input_flag | bool }}"
ratio: "{{ input_ratio | float }}"
items: "{{ input_string | from_json }}"
data: "{{ input_yaml | from_yaml }}"
text: "{{ data_dict | to_json }}"
pretty: "{{ data_dict | to_nice_json }}"
yaml_out: "{{ data_dict | to_yaml }}"

String Filters

# Case
upper: "{{ name | upper }}"              # HELLO
lower: "{{ name | lower }}"              # hello
title: "{{ name | title }}"              # Hello World
capitalize: "{{ name | capitalize }}"    # Hello world

# Manipulation
replaced: "{{ text | replace('old', 'new') }}"
trimmed: "{{ text | trim }}"
truncated: "{{ text | truncate(50) }}"
wrapped: "{{ text | wordwrap(80) }}"

# Regex
match: "{{ text | regex_search('pattern') }}"
replaced: "{{ text | regex_replace('\\d+', 'NUM') }}"
found: "{{ text | regex_findall('\\w+') }}"

# Hash/Encode
md5: "{{ text | md5 }}"
sha1: "{{ text | hash('sha1') }}"
b64: "{{ text | b64encode }}"
decoded: "{{ encoded | b64decode }}"
url: "{{ text | urlencode }}"

# Password
hashed: "{{ 'mypassword' | password_hash('sha512') }}"
hashed_salt: "{{ 'mypassword' | password_hash('sha512', 'mysalt') }}"

List Filters

# Basic operations
first: "{{ mylist | first }}"
last: "{{ mylist | last }}"
length: "{{ mylist | length }}"
sorted: "{{ mylist | sort }}"
reversed: "{{ mylist | reverse | list }}"
unique: "{{ mylist | unique }}"
flat: "{{ nested_list | flatten }}"
shuffled: "{{ mylist | shuffle }}"

# Random
random: "{{ mylist | random }}"
random_num: "{{ range(1, 100) | random }}"

# Math on lists
min_val: "{{ numbers | min }}"
max_val: "{{ numbers | max }}"
sum_val: "{{ numbers | sum }}"

# Set operations
union: "{{ list1 | union(list2) }}"
intersect: "{{ list1 | intersect(list2) }}"
difference: "{{ list1 | difference(list2) }}"
symmetric: "{{ list1 | symmetric_difference(list2) }}"

# Map (extract attribute from list of dicts)
names: "{{ users | map(attribute='name') | list }}"
emails: "{{ users | map(attribute='email') | list }}"

# Select/Reject
adults: "{{ users | selectattr('age', '>=', 18) | list }}"
active: "{{ users | selectattr('status', 'eq', 'active') | list }}"
no_admins: "{{ users | rejectattr('role', 'eq', 'admin') | list }}"

# Join
csv: "{{ mylist | join(',') }}"
lines: "{{ mylist | join('\n') }}"

Dictionary Filters

# Dict operations
keys: "{{ mydict | dict2items }}"
back: "{{ items_list | items2dict }}"
combined: "{{ dict1 | combine(dict2) }}"
deep: "{{ dict1 | combine(dict2, recursive=True) }}"

# Extract values
values: "{{ mydict.values() | list }}"
keys: "{{ mydict.keys() | list }}"

# json_query (JMESPath)
names: "{{ users | json_query('[].name') }}"
admins: "{{ users | json_query('[?role==`admin`].name') }}"
emails: "{{ users | json_query('[].{name: name, email: email}') }}"

Path Filters

# File paths
basename: "{{ '/path/to/file.txt' | basename }}"          # file.txt
dirname: "{{ '/path/to/file.txt' | dirname }}"             # /path/to
extension: "{{ 'file.tar.gz' | splitext }}"                # ('file.tar', '.gz')
expanduser: "{{ '~/docs' | expanduser }}"                  # /home/user/docs
realpath: "{{ '../relative' | realpath }}"                  # /absolute/path

IP Address Filters

# Requires netaddr: pip install netaddr
is_ip: "{{ addr | ansible.utils.ipaddr }}"
v4_only: "{{ addr | ansible.utils.ipv4 }}"
v6_only: "{{ addr | ansible.utils.ipv6 }}"
network: "{{ '192.168.1.0/24' | ansible.utils.ipaddr('network') }}"
netmask: "{{ '192.168.1.0/24' | ansible.utils.ipaddr('netmask') }}"
host_count: "{{ '10.0.0.0/8' | ansible.utils.ipaddr('size') }}"

Date/Time Filters

# Current timestamp
now: "{{ now(utc=true) }}"
epoch: "{{ now(utc=true) | int }}"

# Format dates
formatted: "{{ '%Y-%m-%d' | strftime }}"
from_epoch: "{{ 1703275200 | strftime('%Y-%m-%d %H:%M') }}"

# Date math (human_to_seconds)
seconds: "{{ '1d' | community.general.to_seconds }}"

Practical Patterns

Dynamic Configuration

vars:
  workers: "{{ ansible_processor_vcpus | default(2) | int * 2 }}"
  memory_limit: "{{ (ansible_memtotal_mb * 0.75) | int }}m"
  listen_address: "{{ ansible_default_ipv4.address | default('0.0.0.0') }}"

Conditional Lists

# Build package list based on conditions
packages: >-
  {{ ['nginx', 'curl', 'vim']
     + (['docker-ce'] if install_docker | default(false) | bool else [])
     + (['nodejs'] if install_node | default(false) | bool else []) }}

Parse Command Output

- command: df -h /
  register: disk
  changed_when: false

- set_fact:
    disk_usage: "{{ disk.stdout_lines[1].split()[4] | replace('%','') | int }}"

- assert:
    that: disk_usage < 90
    fail_msg: "Disk usage {{ disk_usage }}% exceeds 90%"

Transform Inventory Data

# Get all IPs from a group
app_ips: "{{ groups['appservers'] | map('extract', hostvars, 'ansible_host') | list }}"

# Create comma-separated list
allowed_hosts: "{{ groups['webservers'] | map('extract', hostvars, 'ansible_host') | join(',') }}"

# Create haproxy backend config
backends: "{{ groups['appservers'] | map('extract', hostvars, ['ansible_host']) | map('regex_replace', '(.*)', 'server \\1 \\1:8080 check') | list }}"

Troubleshooting

Filter Not Found

Install required collections:

# For json_query
pip install jmespath

# For ipaddr
ansible-galaxy collection install ansible.utils
pip install netaddr

Undefined Variable in Filter Chain

Use default() before other filters:

# WRONG — fails if undefined
{{ my_var | int }}

# RIGHT
{{ my_var | default(0) | int }}

Conclusion

Filters are the data transformation layer of Ansible. Essential ones: default() for safe variable access, int/bool/float for type conversion, map()/selectattr()/rejectattr() for list processing, combine() for merging dicts, regex_search()/regex_replace() for string matching, and to_json/from_json for serialization. Chain filters with | to build complex transformations. Master these and you can handle any data manipulation in your playbooks.