Ansible map Filter: Extract Attributes from Lists (Complete Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
Complete guide to the Ansible map filter. Extract attributes from lists of objects, apply filters to list items, chain with select/reject, and transform data.
The Ansible map filter is one of the most powerful Jinja2 filters for transforming lists. It lets you extract a single attribute from a list of objects, apply a filter to every item, or chain complex data transformations — all in a single expression.
Basic Syntax
# Extract an attribute from each item in a list
{{ list_of_dicts | map(attribute='name') | list }}
# Apply a filter to each item
{{ list_of_strings | map('upper') | list }}
# Apply a filter with arguments
{{ list_of_strings | map('regex_replace', '^(.*)$', 'prefix_\\1') | list }}
> Important: Always add | list at the end. The map filter returns a generator, not a list. Without | list, you'll get a generator object instead of usable data.
See also: Ansible default() Filter: Set Fallback Values for Undefined Variables
Extract a Single Attribute
The most common use case — pull one field from a list of dictionaries:
---
- name: Extract attributes with map
hosts: localhost
gather_facts: false
vars:
users:
- name: alice
email: alice@example.com
role: admin
- name: bob
email: bob@example.com
role: developer
- name: charlie
email: charlie@example.com
role: viewer
tasks:
- name: Get all usernames
ansible.builtin.debug:
msg: "{{ users | map(attribute='name') | list }}"
# Output: ['alice', 'bob', 'charlie']
- name: Get all emails
ansible.builtin.debug:
msg: "{{ users | map(attribute='email') | list }}"
# Output: ['alice@example.com', 'bob@example.com', 'charlie@example.com']
Extract Nested Attributes
Access nested dictionary keys using dot notation:
vars:
servers:
- name: web1
network:
ip: 192.168.1.10
subnet: 255.255.255.0
- name: web2
network:
ip: 192.168.1.11
subnet: 255.255.255.0
tasks:
- name: Get all IP addresses
ansible.builtin.debug:
msg: "{{ servers | map(attribute='network.ip') | list }}"
# Output: ['192.168.1.10', '192.168.1.11']
See also: Ansible regex_replace Filter: Find & Replace with Regex (Complete Guide)
Apply a Filter to Every Item
Use map to apply any Jinja2 filter to each element:
vars:
names: ['alice', 'bob', 'charlie']
tasks:
- name: Uppercase all names
ansible.builtin.debug:
msg: "{{ names | map('upper') | list }}"
# Output: ['ALICE', 'BOB', 'CHARLIE']
- name: Wrap each name in quotes
ansible.builtin.debug:
msg: "{{ names | map('regex_replace', '^(.*)$', '\"\\1\"') | list }}"
- name: Add prefix to each item
ansible.builtin.debug:
msg: "{{ names | map('regex_replace', '^', 'user_') | list }}"
# Output: ['user_alice', 'user_bob', 'user_charlie']
- name: Get string lengths
ansible.builtin.debug:
msg: "{{ names | map('length') | list }}"
# Output: [5, 3, 7]
Chain map with selectattr
Combine selectattr to filter, then map to extract:
vars:
users:
- name: alice
active: true
role: admin
- name: bob
active: false
role: developer
- name: charlie
active: true
role: developer
tasks:
- name: Get names of active users only
ansible.builtin.debug:
msg: "{{ users | selectattr('active', 'equalto', true) | map(attribute='name') | list }}"
# Output: ['alice', 'charlie']
- name: Get names of developers
ansible.builtin.debug:
msg: "{{ users | selectattr('role', 'equalto', 'developer') | map(attribute='name') | list }}"
# Output: ['bob', 'charlie']
- name: Get active developer names
ansible.builtin.debug:
msg: >-
{{ users
| selectattr('active', 'equalto', true)
| selectattr('role', 'equalto', 'developer')
| map(attribute='name')
| list }}
# Output: ['charlie']
See also: Ansible Jinja2 Filters: Transform Data in Playbooks (Complete Reference)
Extract Multiple Attributes
The map filter extracts only one attribute at a time. For multiple attributes, use a Jinja2 for-loop or combine with json_query:
Method 1: Multiple map Calls
- name: Get names and emails separately
ansible.builtin.debug:
msg:
names: "{{ users | map(attribute='name') | list }}"
emails: "{{ users | map(attribute='email') | list }}"
Method 2: json_query (Multiple Fields)
- name: Get name and email pairs
ansible.builtin.debug:
msg: "{{ users | json_query('[].{name: name, email: email}') }}"
Method 3: Jinja2 List Comprehension
- name: Build custom list
ansible.builtin.set_fact:
user_summary: >-
{{ users | map(attribute='name') | zip(users | map(attribute='email'))
| map('join', ': ') | list }}
# Output: ['alice: alice@example.com', 'bob: bob@example.com', ...]
Real-World Examples
Get All IP Addresses from Inventory
- name: Collect all IPs from group
ansible.builtin.debug:
msg: "{{ groups['webservers'] | map('extract', hostvars, 'ansible_default_ipv4') | map(attribute='address') | list }}"
Build Package List from Role Variables
vars:
packages:
- name: nginx
version: "1.24"
state: present
- name: redis
version: "7.0"
state: present
- name: mysql
version: "8.0"
state: absent
tasks:
- name: Install only present packages
ansible.builtin.package:
name: "{{ item }}"
state: present
loop: "{{ packages | selectattr('state', 'equalto', 'present') | map(attribute='name') | list }}"
Generate Configuration from List
vars:
dns_servers:
- ip: 8.8.8.8
provider: Google
- ip: 1.1.1.1
provider: Cloudflare
tasks:
- name: Write DNS config
ansible.builtin.copy:
content: |
# DNS Servers
{% for ip in dns_servers | map(attribute='ip') | list %}
nameserver {{ ip }}
{% endfor %}
dest: /etc/resolv.conf
Flatten and Map Nested Structures
vars:
departments:
- name: Engineering
members: ['alice', 'bob']
- name: Marketing
members: ['charlie', 'diana']
tasks:
- name: Get all members across departments
ansible.builtin.debug:
msg: "{{ departments | map(attribute='members') | flatten | list }}"
# Output: ['alice', 'bob', 'charlie', 'diana']
map vs Other Filters
| Filter | Purpose | Example |
|--------|---------|---------|
| map(attribute='x') | Extract attribute from dicts | users \| map(attribute='name') |
| map('filter') | Apply filter to each item | names \| map('upper') |
| selectattr | Filter dicts by attribute value | users \| selectattr('active') |
| select | Filter items by test | numbers \| select('gt', 5) |
| json_query | Complex JMESPath queries | users \| json_query('[].name') |
| items2dict | Convert list of k/v to dict | pairs \| items2dict |
Common Errors
"Generator object" in Output
# Wrong — missing | list
msg: "{{ users | map(attribute='name') }}"
# Output: <generator object...>
# Correct
msg: "{{ users | map(attribute='name') | list }}"
"Undefined attribute" Error
# Handle missing attributes with default
msg: "{{ users | map(attribute='nickname', default='N/A') | list }}"
"Expected string or number" in Loop
# Wrong — map returns objects, not strings
loop: "{{ users | map(attribute='name') }}"
# Correct — convert to list first
loop: "{{ users | map(attribute='name') | list }}"
FAQ
What does the Ansible map filter do?
The map filter iterates over a list and either extracts a specific attribute from each item (when used with attribute=) or applies a Jinja2 filter to each item. It returns a generator that you convert to a list with | list.
How do I extract multiple attributes with map?
You cannot extract multiple attributes in a single map call. Use separate map calls for each attribute, or use json_query with JMESPath: users | json_query('[].{name: name, email: email}').
What is the difference between map and selectattr?
selectattr filters a list of dictionaries by an attribute value (returns matching items). map transforms a list by extracting an attribute or applying a filter (returns the transformed values). They're often chained: selectattr first to filter, then map to extract.
Why do I need | list after map?
The map filter returns a lazy generator object, not a Python list. Most Ansible operations need a real list, so you must pipe through | list to materialize the results.
Can I use map with nested attributes?
Yes, use dot notation: servers | map(attribute='network.ip') | list. This works for dictionaries nested multiple levels deep.
Conclusion
The map filter is essential for working with lists of dictionaries in Ansible. Key patterns to remember:
• Extract attributes: list | map(attribute='field') | list
• Apply filters: list | map('filter_name') | list
• Filter then extract: list | selectattr(...) | map(attribute='field') | list
• Always add | list at the end
Master map and you'll write cleaner, more concise Ansible playbooks with less boilerplate looping.
Related Articles
• Filter a List by Its Attributes: Ansible selectattr Filter • Ansible Combine for Dicts vs Product for Lists • Ansible Flatten: Nested Lists in Playbooks • Ansible split Filter Complete Guide • Print Text or Variable: Ansible Module debugCategory: installation