Complete Guide to Ansible Filters: String, List, Math, and Data Transformation with Examples

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.

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?

RS

About the Author: Ramesh Sundararamaiah

Red Hat Certified Architect

Ramesh is a Red Hat Certified Architect with extensive experience in enterprise Linux environments. He specializes in system administration, DevOps automation, and cloud infrastructure. Ramesh has helped organizations implement robust Linux solutions and optimize their IT operations for performance and reliability.

Expertise: Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, System Administration, DevOps

Add Comment