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 }}
Related Articles
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.