Filters are one of Ansible’s most powerful features, allowing you to transform and manipulate data within playbooks and templates. This comprehensive guide covers all major filter types with practical examples, from basic string manipulation to advanced data transformations, helping you write cleaner and more efficient Ansible automation.
📑 Table of Contents
- What are Ansible Filters?
- Basic Filter Syntax
- Why Use Filters?
- String Filters
- Case Conversion
- String Replacement and Manipulation
- String Testing and Searching
- Regular Expression Filters
- List Filters
- Basic List Operations
- List Joining and Splitting
- Advanced List Operations
- Extracting from List of Dictionaries
- Math and Number Filters
- Basic Math Operations
- Size and Memory Conversions
- Type Conversion Filters
- Default and Fallback Filters
- JSON and YAML Filters
- Path and Filesystem Filters
- Date and Time Filters
- IP Address and Network Filters
- Hashing and Encoding Filters
- Dictionary Filters
- Ternary and Conditional Filters
- Random Filters
- Filter Chaining
- Custom Filters in Playbooks
- Best Practices for Using Filters
- 1. Use Appropriate Filters
- 2. Chain Filters for Readability
- 3. Use Type Conversion
- 4. Validate with Mandatory
- Common Pitfalls and Solutions
- Issue: Undefined Variable Error
- Issue: Empty String vs Undefined
- Issue: Type Mismatch
- Summary
What are Ansible Filters?
Filters in Ansible are functions that transform data. They’re based on Jinja2’s filter system and allow you to modify variables, format output, perform calculations, and manipulate data structures without writing custom Python code.
Basic Filter Syntax
# Basic syntax: variable | filter
{{ variable_name | filter_name }}
# Filter with arguments
{{ variable_name | filter_name(argument) }}
# Chaining multiple filters
{{ variable_name | filter1 | filter2 | filter3 }}
Why Use Filters?
- Data Transformation: Convert data types, formats, and structures
- String Manipulation: Case conversion, truncation, replacement
- List Operations: Sorting, filtering, extracting values
- Math Operations: Calculations and conversions
- Type Safety: Ensure data is in the correct format
- Code Readability: Clean, declarative data transformations
String Filters
String filters help you manipulate text data – one of the most common operations in automation.
Case Conversion
vars:
app_name: "MyWebApplication"
tasks:
- name: String case conversion examples
debug:
msg:
- "Original: {{ app_name }}"
- "Lowercase: {{ app_name | lower }}" # mywebapplication
- "Uppercase: {{ app_name | upper }}" # MYWEBAPPLICATION
- "Title Case: {{ app_name | title }}" # Mywebapplication
- "Capitalize: {{ app_name | capitalize }}" # Mywebapplication
Use Case: Generate configuration file names in lowercase:
- name: Create config file with lowercase name
template:
src: config.j2
dest: "/etc/{{ app_name | lower }}.conf"
# Creates: /etc/mywebapplication.conf
String Replacement and Manipulation
vars:
server_name: "web-server-01"
description: "This is a very long description that needs to be truncated"
tasks:
- name: String manipulation examples
debug:
msg:
- "Replace: {{ server_name | replace('-', '_') }}" # web_server_01
- "Replace hyphen with space: {{ server_name | replace('-', ' ') }}" # web server 01
- "Truncate to 20 chars: {{ description | truncate(20) }}" # This is a very lo...
- "Truncate no ellipsis: {{ description | truncate(20, True, '') }}" # This is a very long
- "Trim whitespace: {{ ' text ' | trim }}" # text
- "Center (30 chars): {{ 'Title' | center(30) }}" # Title
String Testing and Searching
- name: String testing with filters
debug:
msg: "{{ item }}"
loop:
- "Starts with 'web': {{ server_name | regex_search('^web') }}"
- "Contains 'server': {{ 'server' in server_name }}"
- "Length: {{ server_name | length }}"
- "Reverse: {{ server_name | reverse }}"
Regular Expression Filters
vars:
version_string: "application-v2.1.0.tar.gz"
log_line: "Error: Connection failed at 192.168.1.100:8080"
tasks:
- name: Regex operations
debug:
msg:
# Extract version number (2.1.0)
- "Version: {{ version_string | regex_search('v([0-9.]+)', '\1') }}"
# Replace all digits with X
- "Masked: {{ log_line | regex_replace('[0-9]', 'X') }}"
# Extract IP address
- "IP: {{ log_line | regex_search('([0-9]{1,3}\.){3}[0-9]{1,3}') }}"
# Find all numbers
- "Numbers: {{ log_line | regex_findall('[0-9]+') }}"
Real-World Example: Extract version from package name:
- name: Get application version
set_fact:
app_version: "{{ package_file | regex_search('-([0-9.]+)\.rpm$', '\1') | first }}"
vars:
package_file: "myapp-2.3.1.rpm"
# Result: app_version = "2.3.1"
List Filters
List filters help you manipulate arrays and extract data from collections.
Basic List Operations
vars:
servers: ['web01', 'web02', 'db01', 'cache01', 'web03']
numbers: [10, 5, 20, 15, 30, 5]
tasks:
- name: Basic list operations
debug:
msg:
- "First item: {{ servers | first }}" # web01
- "Last item: {{ servers | last }}" # web03
- "Length: {{ servers | length }}" # 5
- "Random item: {{ servers | random }}" # random selection
- "Sorted: {{ servers | sort }}" # alphabetically sorted
- "Reverse sorted: {{ servers | sort(reverse=true) }}"
- "Unique values: {{ numbers | unique }}" # [10, 5, 20, 15, 30]
- "Min value: {{ numbers | min }}" # 5
- "Max value: {{ numbers | max }}" # 30
- "Sum: {{ numbers | sum }}" # 85
List Joining and Splitting
vars:
server_list: ['web01', 'web02', 'web03']
csv_data: "name,age,city"
tasks:
- name: Join and split examples
debug:
msg:
- "Join with comma: {{ server_list | join(', ') }}" # web01, web02, web03
- "Join with newline: {{ server_list | join('\n') }}"
- "Split CSV: {{ csv_data | split(',') }}" # ['name', 'age', 'city']
Advanced List Operations
vars:
list1: [1, 2, 3, 4]
list2: [3, 4, 5, 6]
tasks:
- name: Advanced list operations
debug:
msg:
- "Union: {{ list1 | union(list2) }}" # [1, 2, 3, 4, 5, 6]
- "Intersect: {{ list1 | intersect(list2) }}" # [3, 4]
- "Difference: {{ list1 | difference(list2) }}" # [1, 2]
- "Symmetric diff: {{ list1 | symmetric_difference(list2) }}" # [1, 2, 5, 6]
- "Flatten nested: {{ [[1, 2], [3, 4]] | flatten }}" # [1, 2, 3, 4]
- "Batch (groups of 2): {{ [1,2,3,4,5,6] | batch(2) | list }}"
Extracting from List of Dictionaries
vars:
users:
- name: alice
email: alice@example.com
role: admin
active: true
- name: bob
email: bob@example.com
role: developer
active: true
- name: charlie
email: charlie@example.com
role: viewer
active: false
tasks:
- name: Extract from list of dicts
debug:
msg:
# Get all names
- "Names: {{ users | map(attribute='name') | list }}"
# Get all emails
- "Emails: {{ users | map(attribute='email') | list }}"
# Filter active users
- "Active users: {{ users | selectattr('active') | map(attribute='name') | list }}"
# Filter by role
- "Admins: {{ users | selectattr('role', 'equalto', 'admin') | map(attribute='name') | list }}"
# Reject inactive
- "Active only: {{ users | rejectattr('active', 'equalto', false) | map(attribute='name') | list }}"
Real-World Example: Get all IP addresses from server list:
vars:
servers:
- name: web01
ip: 192.168.1.10
- name: web02
ip: 192.168.1.11
- name: db01
ip: 192.168.1.20
- name: Generate /etc/hosts entries
lineinfile:
path: /etc/hosts
line: "{{ item.ip }} {{ item.name }}"
loop: "{{ servers }}"
- name: Get all IPs for firewall rule
set_fact:
allowed_ips: "{{ servers | map(attribute='ip') | join(',') }}"
# Result: "192.168.1.10,192.168.1.11,192.168.1.20"
Math and Number Filters
Basic Math Operations
vars:
price: 99.99
negative_value: -42
memory_mb: 8192
tasks:
- name: Math filter examples
debug:
msg:
- "Absolute value: {{ negative_value | abs }}" # 42
- "Round: {{ price | round }}" # 100
- "Round to 2 decimals: {{ price | round(2) }}" # 99.99
- "Ceiling: {{ price | round(0, 'ceil') }}" # 100
- "Floor: {{ price | round(0, 'floor') }}" # 99
- "Power of 2: {{ 2 | pow(8) }}" # 256
- "Square root: {{ 16 | root(2) }}" # 4.0
- "Logarithm: {{ 100 | log(10) }}" # 2.0
Size and Memory Conversions
- name: Memory conversions
debug:
msg:
- "Bytes to human: {{ 1234567890 | human_readable }}" # 1.15 GB
- "MB to GB: {{ memory_mb | int / 1024 | round(2) }}" # 8.0
- "Exact bytes: {{ 1234567890 | human_readable(unit='M') }}" # 1177.38 MB
- "Binary units: {{ 1073741824 | human_readable }}" # 1.0 GiB
Real-World Example: Calculate optimal database pool size:
- name: Set database pool size based on memory
set_fact:
db_pool_size: "{{ ((ansible_memtotal_mb * 0.25) / 10) | round | int }}"
# For 8GB RAM: (8192 * 0.25) / 10 = 204.8 -> 205 connections
Type Conversion Filters
vars:
string_number: "42"
float_string: "3.14"
number: 123
boolean_string: "yes"
tasks:
- name: Type conversion examples
debug:
msg:
- "String to int: {{ string_number | int }}" # 42
- "String to float: {{ float_string | float }}" # 3.14
- "Number to string: {{ number | string }}" # "123"
- "String to bool: {{ boolean_string | bool }}" # True
- "List to string: {{ ['a', 'b', 'c'] | string }}" # "['a', 'b', 'c']"
Real-World Example: Ensure port number is integer:
- name: Configure application port
lineinfile:
path: /etc/app/config.conf
regexp: '^port='
line: "port={{ app_port | int }}"
vars:
app_port: "8080" # Even if passed as string, ensures it's an integer
Default and Fallback Filters
- name: Default value examples
debug:
msg:
# Use default if variable undefined
- "DB Host: {{ db_host | default('localhost') }}"
# Use default even for empty strings (true parameter)
- "Admin Email: {{ admin_email | default('admin@example.com', true) }}"
# Mandatory - fail if not defined
- "API Key: {{ api_key | mandatory }}"
# Default to omit (excludes parameter from task)
- "Backup: {{ backup_enabled | default(omit) }}"
Real-World Example: Safe configuration with defaults:
- name: Deploy application config
template:
src: app.conf.j2
dest: /etc/app/app.conf
vars:
# Template content
db_host: "{{ database_host | default('localhost') }}"
db_port: "{{ database_port | default(5432) }}"
cache_ttl: "{{ cache_timeout | default(3600) }}"
debug_mode: "{{ debug | default(false) }}"
JSON and YAML Filters
vars:
config_dict:
database:
host: localhost
port: 5432
cache:
enabled: true
ttl: 3600
json_string: '{"name": "test", "value": 123}'
tasks:
- name: JSON/YAML conversion
debug:
msg:
# Convert dict to JSON
- "Compact JSON: {{ config_dict | to_json }}"
# Pretty JSON
- "Pretty JSON: {{ config_dict | to_nice_json }}"
# Convert to YAML
- "YAML: {{ config_dict | to_yaml }}"
# Pretty YAML
- "Nice YAML: {{ config_dict | to_nice_yaml }}"
# Parse JSON string
- "From JSON: {{ json_string | from_json }}"
# Parse YAML string
- "From YAML: {{ yaml_string | from_yaml }}"
Real-World Example: Store configuration as JSON:
- name: Write config as JSON file
copy:
content: "{{ app_config | to_nice_json }}"
dest: /etc/app/config.json
vars:
app_config:
version: "2.1.0"
features:
cache: true
debug: false
Path and Filesystem Filters
vars:
file_path: "/var/www/html/app/config/database.yml"
user_path: "~/documents/file.txt"
tasks:
- name: Path manipulation
debug:
msg:
- "Basename: {{ file_path | basename }}" # database.yml
- "Dirname: {{ file_path | dirname }}" # /var/www/html/app/config
- "Expand home: {{ user_path | expanduser }}" # /home/user/documents/file.txt
- "Real path: {{ '.' | realpath }}" # /current/working/directory
- "Split ext: {{ file_path | splitext }}" # ['/var/www/html/app/config/database', '.yml']
- "Win path: {{ 'C:\Users\file.txt' | win_dirname }}" # C:Users
- "Win basename: {{ 'C:\Users\file.txt' | win_basename }}" # file.txt
Real-World Example: Extract filename for logging:
- name: Process uploaded files
debug:
msg: "Processing file: {{ item | basename }}"
loop:
- /uploads/document1.pdf
- /uploads/image.jpg
- /uploads/data.csv
Date and Time Filters
- name: Date and time operations
debug:
msg:
- "Current time: {{ ansible_date_time.iso8601 }}"
- "Epoch: {{ ansible_date_time.epoch }}"
- "Today: {{ '%Y-%m-%d' | strftime }}"
- "Custom format: {{ '%A, %B %d, %Y at %H:%M' | strftime }}"
- "From epoch: {{ ansible_date_time.epoch | int | to_datetime }}"
- "Add timestamp: {{ 'backup_' + ansible_date_time.date + '.tar.gz' }}"
Real-World Example: Create timestamped backups:
- name: Backup database with timestamp
command: >
mysqldump -u root database_name >
/backups/db_{{ ansible_date_time.date }}_{{ ansible_date_time.time }}.sql
args:
creates: "/backups/db_{{ ansible_date_time.date }}_{{ ansible_date_time.time }}.sql"
IP Address and Network Filters
vars:
ip_with_cidr: "192.168.1.50/24"
ipv6_addr: "2001:db8::1"
tasks:
- name: IP address operations
debug:
msg:
- "Is IP: {{ '192.168.1.1' | ipaddr }}" # True
- "Is IPv4: {{ '192.168.1.1' | ipv4 }}" # True
- "Is IPv6: {{ ipv6_addr | ipv6 }}" # True
- "Network: {{ ip_with_cidr | ipaddr('network') }}" # 192.168.1.0
- "Netmask: {{ ip_with_cidr | ipaddr('netmask') }}" # 255.255.255.0
- "Broadcast: {{ ip_with_cidr | ipaddr('broadcast') }}" # 192.168.1.255
- "Host IP: {{ ip_with_cidr | ipaddr('address') }}" # 192.168.1.50
- "Prefix: {{ ip_with_cidr | ipaddr('prefix') }}" # 24
Real-World Example: Generate subnet configuration:
- name: Configure network interface
template:
src: interface.j2
dest: /etc/network/interfaces
vars:
interface_config:
address: "{{ ip_with_cidr | ipaddr('address') }}"
netmask: "{{ ip_with_cidr | ipaddr('netmask') }}"
network: "{{ ip_with_cidr | ipaddr('network') }}"
broadcast: "{{ ip_with_cidr | ipaddr('broadcast') }}"
Hashing and Encoding Filters
vars:
password: "secret123"
data: "Hello World"
tasks:
- name: Hashing and encoding
debug:
msg:
- "MD5: {{ password | hash('md5') }}"
- "SHA1: {{ password | hash('sha1') }}"
- "SHA256: {{ password | hash('sha256') }}"
- "SHA512: {{ password | hash('sha512') }}"
- "Base64 encode: {{ data | b64encode }}" # SGVsbG8gV29ybGQ=
- "Base64 decode: {{ data | b64encode | b64decode }}" # Hello World
- "URL encode: {{ 'hello world' | urlencode }}" # hello+world
- "Password hash: {{ password | password_hash('sha512') }}"
Real-World Example: Create user with hashed password:
- name: Create user with secure password
user:
name: webadmin
password: "{{ user_password | password_hash('sha512', 'mysalt') }}"
update_password: on_create
vars:
user_password: "ChangeMe123!"
Dictionary Filters
vars:
config:
database:
host: db.example.com
port: 5432
cache:
host: redis.local
port: 6379
tasks:
- name: Dictionary operations
debug:
msg:
# Convert to list of key-value pairs
- "Items: {{ config | dict2items }}"
# Get all keys
- "Keys: {{ config.keys() | list }}"
# Get all values
- "Values: {{ config.values() | list }}"
# Combine dictionaries
- "Combined: {{ config | combine({'new_key': 'value'}) }}"
# Deep merge
- "Merged: {{ defaults | combine(overrides, recursive=True) }}"
Real-World Example: Merge default and custom configs:
- name: Merge configurations
set_fact:
final_config: "{{ default_config | combine(custom_config, recursive=True) }}"
vars:
default_config:
timeout: 30
retries: 3
cache:
enabled: true
ttl: 3600
custom_config:
timeout: 60
cache:
ttl: 7200
# Result: timeout=60, retries=3, cache.enabled=true, cache.ttl=7200
Ternary and Conditional Filters
vars:
environment: "production"
ssl_enabled: true
tasks:
- name: Ternary operator examples
debug:
msg:
- "{{ (environment == 'production') | ternary('prod', 'dev') }}"
- "SSL: {{ ssl_enabled | ternary('enabled', 'disabled') }}"
- "Debug: {{ (environment != 'production') | ternary('on', 'off') }}"
- "Mode: {{ ssl_enabled | ternary('secure', 'insecure', 'unknown') }}"
Real-World Example: Environment-specific settings:
- name: Set configuration based on environment
template:
src: app.conf.j2
dest: /etc/app/app.conf
vars:
log_level: "{{ (environment == 'production') | ternary('WARNING', 'DEBUG') }}"
cache_enabled: "{{ (environment == 'production') | ternary('true', 'false') }}"
pool_size: "{{ (environment == 'production') | ternary(50, 5) }}"
Random Filters
- name: Random value generation
debug:
msg:
- "Random number (0-100): {{ 100 | random }}"
- "Random with step: {{ 100 | random(step=10) }}" # 0, 10, 20, ... 100
- "Random with seed: {{ 100 | random(seed=inventory_hostname) }}"
- "Random MAC: {{ '52:54:00' | random_mac }}" # 52:54:00:XX:XX:XX
- "Random item: {{ ['red', 'blue', 'green'] | random }}"
- "Shuffle list: {{ [1, 2, 3, 4, 5] | shuffle }}"
Real-World Example: Load balancing with random selection:
- name: Select random backend server
set_fact:
selected_backend: "{{ backend_servers | random }}"
vars:
backend_servers:
- backend1.example.com
- backend2.example.com
- backend3.example.com
Filter Chaining
Combine multiple filters for complex transformations:
vars:
users:
- name: Alice Smith
email: ALICE@EXAMPLE.COM
active: true
- name: Bob Jones
email: bob@example.com
active: true
- name: Charlie Brown
email: charlie@example.com
active: false
tasks:
- name: Complex filter chains
debug:
msg:
# Get active user emails in lowercase, sorted
- "{{ users | selectattr('active') | map(attribute='email') | map('lower') | sort | list }}"
# Get uppercase names, joined with comma
- "{{ users | map(attribute='name') | map('upper') | join(', ') }}"
# Extract, filter, transform
- "{{ servers | map(attribute='hostname') | select('match', '^web') | map('upper') | list }}"
Real-World Example: Generate comma-separated admin emails:
- name: Send notification to all active admins
mail:
to: "{{ users | selectattr('active') | selectattr('role', 'equalto', 'admin') | map(attribute='email') | map('lower') | join(',') }}"
subject: "System Alert"
body: "Critical system event detected"
Custom Filters in Playbooks
While Ansible has extensive built-in filters, you can create custom filters for specific needs:
# filter_plugins/custom_filters.py
def reverse_string(value):
return value[::-1]
def multiply_by_ten(value):
return int(value) * 10
class FilterModule(object):
def filters(self):
return {
'reverse_string': reverse_string,
'multiply_by_ten': multiply_by_ten
}
# Use in playbook
- name: Use custom filters
debug:
msg:
- "{{ 'hello' | reverse_string }}" # olleh
- "{{ 5 | multiply_by_ten }}" # 50
Best Practices for Using Filters
1. Use Appropriate Filters
# Good - use default filter
db_host: "{{ database_host | default('localhost') }}"
# Bad - manual condition
db_host: "{% if database_host is defined %}{{ database_host }}{% else %}localhost{% endif %}"
2. Chain Filters for Readability
# Good - clear pipeline
admin_emails: "{{ users | selectattr('admin') | map(attribute='email') | join(',') }}"
# Bad - nested operations
admin_emails: "{% set emails = [] %}{% for u in users %}{% if u.admin %}{{ emails.append(u.email) }}{% endif %}{% endfor %}{{ emails | join(',') }}"
3. Use Type Conversion
# Good - explicit type conversion
port: "{{ app_port | int }}"
timeout: "{{ timeout_seconds | float }}"
# Bad - assuming types
port: "{{ app_port }}" # Might be string
4. Validate with Mandatory
# Good - fail fast if missing
api_key: "{{ api_key | mandatory }}"
database_password: "{{ db_password | mandatory }}"
# Bad - runtime failure later
api_key: "{{ api_key }}"
Common Pitfalls and Solutions
Issue: Undefined Variable Error
# Problem
database_host: "{{ db_host }}" # Fails if db_host undefined
# Solution
database_host: "{{ db_host | default('localhost') }}"
Issue: Empty String vs Undefined
# Problem - empty string not caught
email: "{{ user_email | default('admin@example.com') }}" # Empty string passes
# Solution - treat empty as undefined
email: "{{ user_email | default('admin@example.com', true) }}"
Issue: Type Mismatch
# Problem
retries: "{{ retry_count }}" # Might be string "5"
# Solution
retries: "{{ retry_count | int }}" # Always integer
Summary
Ansible filters provide powerful data transformation capabilities:
- String Filters: Case conversion, replacement, truncation, regex
- List Filters: Sorting, filtering, extracting, set operations
- Math Filters: Calculations, rounding, conversions
- Type Filters: Convert between strings, numbers, booleans
- Default Filters: Handle undefined variables safely
- JSON/YAML: Data format conversions
- Path Filters: Filesystem path manipulation
- Date/Time: Timestamp formatting and manipulation
- Network: IP address operations
- Hash/Encode: Security and encoding operations
Master these filters to write cleaner, more maintainable Ansible automation with less custom code and better error handling.
Was this article helpful?