# List options in modules: "add" or "set"? Let's talk about terminology

**URL:** <https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488>\
**Category:** Collection Development\
**Tags:** aws, code-conventions\
**Created:** [October 16, 2026, 10:22pm UTC](https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488 "2026-10-16T22:22:24Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![russoz](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/russoz/32/127_2.png) [@russoz](https://forum.ansible.com/u/russoz)\
**Post date:** [October 16, 2026, 10:22pm UTC](https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488/1 "2026-10-16T22:22:24Z")

</div>

Many modules manage something that is a collection of things: group members, tags, DNS records, repositories, SSH keys, interfaces. The usual interface is a `type: list` option, and sooner or later every author of such a module has to answer the same question:

> With `state: present` and `things: [a, b, c]`, should the target end up with **exactly** `[a, b, c]`, or should `a`, `b`, and `c` simply be **added** to whatever is already there?

There is no single right answer: both behaviors are legitimate and both are needed. What is missing, as far as I can tell, is a **shared vocabulary** for them. Each module answers the question in its own way, with its own option name, its own polarity, and its own default. Users have to read the docs of every module to find out which of the possible behaviors they get (and sometimes the docs do not say).

This post is **not** a proposal for a “right way” to handle lists. It is an invitation to compare notes and, if there is interest, to agree on names for the different strategies that already exist, so that modules choosing the same strategy can describe it with the same words.

## More than one question

“Add or set” is the most visible part of the problem, but there are a few related questions hiding behind it:

1. **Items not listed** : are they kept, or removed?
2. **Option omitted vs. empty list** : does leaving `things` out mean “do not touch”, and does `things: []` mean “remove everything”? Or are they the same?
3. **Duplicates and order** : is the target a set, an ordered sequence, or a sequence that allows repetitions? Is `[a, b]` the same as `[b, a]`? What happens with `[a, a]`?
4. **Scope** : when “removing what is not listed”, removing it from _where_? Everything on the target, or only within some subset (an access level, a group of interfaces)?
5. **Item identity** : when the list contains dictionaries (an interface with its settings, a member with its access level), is an existing item updated, replaced wholesale, or left alone?

## What community.general does today

A survey of `community.general` modules found at least these strategies. Module names are examples, not an exhaustive list.

| Strategy | How it is expressed | Examples (default) |
| --- | --- | --- |
| Always exact, no switch | Not expressed: passing `[]` clears the list | Most `ipa_*` modules (`ipa_role`, `ipa_sudorule`, `ipa_hbacrule`, …), `consul_token`, `consul_role`, `influxdb_user` |
| Boolean, `true` = add | `append`, `array_add`, `namespace_append` | `ipa_group`, `ipa_hostgroup` (`false`, so exact by default), `osx_defaults`, `pmem` |
| Boolean, `true` = exact | `exclusive` | `ini_file` (`true`), `ipa_dnsrecord` (`true`) |
| | `solo` | `cloudflare_dns`, `dnsimple`, `netcup_dns` (`false`) |
| | `purge` | `gitlab_label`, `gitlab_milestone`, `gitlab_*_variable`, `rhsm_repository` (`false`) |
| | `purge_tags` | `ali_instance` (`false`) |
| | `remove_others` | `apt_repo` (`false`) |
| | `remove_extra_pvs` | `lvg` (`true`) |
| Choice option | `add` / `replace-all` | `git_config` (`replace-all`) |
| | `merge` / `replace` | `manageiq_group` (`replace`) |
| | `replace` / `add` | `osx_defaults` for dictionaries (`replace`) |
| Exact, limited to a scope | `purge_users: [<access levels>]` | `gitlab_project_members`, `gitlab_group_members` (no purge when omitted) |
| A value of `state` | `state: present / absent / exact` | `ldap_attrs` (`present`) |
| Add/remove only | `state: present / absent` applied to the listed items, no exact mode | `manageiq_tags`, `manageiq_policies`, package managers |
| Not documented | Plain `type: list` without saying what happens to existing items | Several modules that handle tags, labels, profiles, DNS server lists |

A few things stand out:

- **The same concept has many names.** “Make the target match the list exactly” is spelled `exclusive`, `solo`, `purge`, `purge_tags`, `remove_others`, `remove_extra_pvs`, `replace`, `replace-all`, `exact`, and `append: false`.
- **The same name means different things.** `purge` is “remove unlisted labels” in `gitlab_label`, but “delete untracked files” in `hg`. `replace` is “replace all values” in `git_config`, but “recreate the vault” in `ipa_vault`.
- **Polarity is not consistent.** For `append` and `array_add`, `true` means “add”. For `exclusive`, `solo`, and `purge`, `true` means “exact”. `ipa_group` with `append: false` and `ini_file` with `exclusive: true` do the same thing.
- **Defaults are not consistent.** Some modules default to the exact behavior (`lvg`, `ini_file`, `ipa_dnsrecord`, `ipa_group`), others to the additive one (`apt_repo`, `rhsm_repository`, `cloudflare_dns`).
- **Duplicates and order are rarely documented.** Some modules compare as sets, some deduplicate the input, some deliberately allow repeated values (`git_config` with `add_mode: add`, `ini_file` with `exclusive: false`).
- **Loops do not combine well with the exact strategies.** `rhsm_repository` and `ansible.posix.authorized_key` both warn that their exact mode is applied per loop iteration.

## Elsewhere in the ecosystem

Other projects have faced the same question, with different answers:

- **`ansible.builtin.user`** has `append` (default `false`) for `groups`. The documentation of the development branch states that from ansible-core 2.21, `append` is required whenever `groups` is specified. In other words, the user must always state which behavior they want.
- **`ansible.posix.authorized_key`** has `exclusive` (default `false`).
- **`amazon.aws`** uses `purge_<thing>` booleans across the collection, for example `purge_tags` (default `true`). The docs make the “omitted vs. empty” distinction explicit: if `tags` is not set, tags are not modified, even with `purge_tags: true`.
- **Network resource modules** (for example `cisco.ios.ios_vlans`) express the strategy through `state`, with a vocabulary shared across many collections: `merged` (add or update the listed items), `replaced` (replace the configuration of the listed items, leave the others alone), `overridden` (the listed items become the entire configuration), and `deleted`. This vocabulary also separates two things that are often mixed up: replacing _an item_ versus replacing _the whole collection_.
- **`community.docker.docker_container`** has `comparisons`, which can be set per option to `strict`, `ignore`, or `allow_more_present`. This is about comparing rather than changing, but it names the same three ideas: exact, not managed, and additive.

## A different shape: each item carries its own state

When the list contains dictionaries, a module could take yet another route. Consider a hypothetical module that manages network interfaces:

```yaml
- some_interface_module:
    state: present # default for items that do not set their own
    interfaces:
      - name: link1 # created or updated
        listen_port: 51821
      - name: old_link
        state: absent # removed

```

- Each item has its own `state`, and the module-wide `state` is the default for the items that do not set one.
- Interfaces that are not listed are never touched. Removing one means listing it with `state: absent`.
- A single task can add some items and remove others.
- A separate option, say `managed_interfaces`, could declare the full set of interfaces the module considers its own, independently of which ones are written in this run. That would make the exact strategy safer: unlisted items would only be removed _within that declared scope_, similar to what `purge_users` does in the GitLab member modules.

Lists nested inside each item (say, `addresses` or `dns`) would still need their own answer to all the questions above, and it is not necessarily the same answer as for the outer list.

## Concepts that seem to need names

Without picking any of them, these are the distinct behaviors that show up above, with the words already in use for each:

| Behavior | Words already in use |
| --- | --- |
| Listed items are ensured, other items are kept | `append`, `add`, `merge`, `merged`, `allow_more_present`, `exclusive: false`, `solo: false`, `purge: false` |
| The target ends up with exactly the listed items | `exact`, `exclusive`, `solo`, `purge`, `purge_*`, `remove_others`, `remove_extra_*`, `replace`, `replace-all`, `overridden`, `strict`, `append: false` |
| Listed items are removed, other items are kept | `state: absent`, `deleted` |
| Exact, but only within a declared subset | `purge_users: [<levels>]` |
| Each listed item declares its own desired state | no common name (the hypothetical `<list>[].state` above) |
| An existing item is replaced as a whole, not merged | `replaced` (network resource modules) |
| The list is not managed at all | option omitted, `ignore` |

Separately from those, it might help to have standard phrasing for how values are compared: as a set, as an ordered sequence, or as a sequence that allows repetitions.

## Dictionaries have the same problem

This post focuses on lists, but `type: dict` options raise very similar questions: are keys that are not specified kept or removed, does an omitted option differ from `{}`, and are nested values merged or replaced? The same variety shows up there too, for example `merge_profile` in `community.general.lxd_profile`, `dict_mode` in `community.general.osx_defaults`, `purge_tags` for the `tags` dictionaries in `amazon.aws`, and `allow_more_present` for dictionaries in `community.docker`. Dictionaries also add a question of their own: how deep a merge goes. Whatever terminology comes out of this discussion would probably need to work for dictionaries too, but it seems easier to start with lists.

## Questions for discussion

- Is a shared terminology for these strategies useful, or is the variety a feature, given how different the managed resources are?
- If it is useful, should it cover only **names** (so that, say, “exact” means the same thing everywhere it appears), or also **how** the choice is exposed (boolean vs. choice option vs. `state` value)?
- Is the network resource vocabulary (`merged`, `replaced`, `overridden`, `deleted`) a good base to build on outside network modules, or is it too tied to `state` and to device configuration?
- Should “option omitted means not managed” and “empty list means clear it” be the expected default meanings, as `amazon.aws` documents for `tags`?
- Should the expected way to handle duplicates and ordering be stated in the docs every time?
- Where should such a convention live: in the Ansible developer guide, or in each collection’s contributor docs?
- Are there other strategies or names in your collections that are not covered here?

Any experience from maintaining modules like these, including what users found confusing, would be very welcome.

* * *

_AI was used to survey the modules and to draft this post._

---

<div class="post-metadata">

**Author:** ![felixfontein](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/felixfontein/32/7530_2.png) [@felixfontein](https://forum.ansible.com/u/felixfontein)\
**Post date:** [October 17, 2026, 10:27am UTC](https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488/2 "2026-10-17T10:27:34Z")

</div>

> [@russoz](#):
>
> 1. **Items not listed** : are they kept, or removed?

Isn’t this basically “add or set”?

> [@russoz](#):
>
> 1. **Option omitted vs. empty list** : does leaving `things` out mean “do not touch”, and does `things: []` mean “remove everything”? Or are they the same?

There’s also another twist: omitted can also mean “ignore for idempotency, and use default (usually empty list) when re-creating the object”. This is for example something the community.docker.docker\_container module does. (But doing it the other way would also be possible. It again depends on what the user wants which behavior is preferred.)

I guess this also depends on whether objects can actually be updated, or only re-created. In the latter case, there’s also the question on what should happen with attributes the module is unaware of. Should these not be set on the newly created object, or should the module try to copy these (which can cause a lot of trouble)?

Generally no matter which choice you make, there will be users filing bug reports about the behavior. Making the behavior configurable allows users to get whatever they want, but also makes the module’s UX more complex, especially if you have a configuration per option (expect if you hide it away behind one big option like community.docker.docker\_container’s `comparison` option).

> [@russoz](#):
>
> 1. **Duplicates and order** : is the target a set, an ordered sequence, or a sequence that allows repetitions? Is `[a, b]` the same as `[b, a]`? What happens with `[a, a]`?

In many cases, this depends (and should depend) on what the option is about. There are few cases (I’m aware of) where making this configurable could make sense.

> [@russoz](#):
>
> - Is a shared terminology for these strategies useful, or is the variety a feature, given how different the managed resources are?

I think it would be good to have a shared terminology. (I’m aware that it will take years to reach it for a large subset, and will never happen for everything, but I still think it’s a good thing.)

> [@russoz](#):
>
> - If it is useful, should it cover only **names** (so that, say, “exact” means the same thing everywhere it appears), or also **how** the choice is exposed (boolean vs. choice option vs. `state` value)?

I think both.

> [@russoz](#):
>
> - Is the network resource vocabulary (`merged`, `replaced`, `overridden`, `deleted`) a good base to build on outside network modules, or is it too tied to `state` and to device configuration?

This is one of the aspects of resource modules I really like (there are others I don’t). I’m not sure whether having this as `state` is the best idea, and it can be very useful to have it more fine-grained - especially in cases where objects need to be completely re-created and can’t be updated, like Docker containers.

It would be good to have something that works for all kind of modules, and not only for modules that can update state without having to re-create the underlying objects.

> [@russoz](#):
>
> - Should “option omitted means not managed” and “empty list means clear it” be the expected default meanings, as `amazon.aws` documents for `tags`?

As I mentioned above, there’s a third option (which is mostly relevant for modules that need to re-create the underlying objects and can’t edit them). Generally I expect non-specified options (for object state) to be ignored during idempotency checks. Whether an explicit empty list means nothing or “replace” should depend on the “add vs. set” configuration.

> [@russoz](#):
>
> - Should the expected way to handle duplicates and ordering be stated in the docs every time?

That would be best, I think.

> [@russoz](#):
>
> - Where should such a convention live: in the Ansible developer guide, or in each collection’s contributor docs?

I’d prefer the Ansible developer guide if it’s a community-wide convention. Collections can have more specific guidelines, but the general thing should be in the Ansible docs.

---

<div class="post-metadata">

**Author:** ![tremble](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/tremble/32/247_2.png) [@tremble](https://forum.ansible.com/u/tremble)\
**Post date:** [October 18, 2026, 3:37pm UTC](https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488/3 "2026-10-18T15:37:29Z")

</div>

> 1. Should “option omitted means not managed” and “empty list means clear it” be the expected default meanings, as `amazon.aws` documents for `tags`?

As one of the people involved in the AWS collection discussions, this is my preferred behaviour. To me “omitted” means “you’re not responsible for this.”

Generic reasons

- This is consistent with a number of core modules like `ansible.builtin.file`, `ansible.builtin.user`, `ansible.builtin.cron`.
- When you have more complex resources, it’s easier to tune just the piece you want rather than the needing to describe the whole thing (And the alternative to this is an explosion in modules)
- When you add new features to the module, you don’t risk suddenly nuking configuration that someone had to manually set, or set through a command because it wasn’t supported.

More “AWS specific” reasons:

- Amazon add features pretty regularly, to avoid forcing people to aggressively upgrade the SDK we need to have the distinction between “I’m **trying** to change something and my SDK doesn’t support it” and “The module was updated to support a feature I’m not using, I don’t care if you can’t read/write it”.
- The AWS permissions model is flexible. It’s not uncommon for someone to be managing a resource and only being permitted to touch specific parameters, where you need to make a similar distinction.

> 1. Should the expected way to handle duplicates and ordering be stated in the docs every time?

In my opinion, until there’s a well adopted convention it needs to be documented in the module. We shouldn’t expect users to look in lots of different place to figure out

- Does this module have a convention? (not a given)
- Does the collection have a convention? (not a given, we took the deliberate step in the AWS collections to try and clean this up) This gets more complex if you’re using modules across multiple collections.
- Does the community as a whole have a convention? (Not today)

> 1. Where should such a convention live: in the Ansible developer guide, or in each collection’s contributor docs?

My personal preference would be the developer guide, but collections should be free to duplicate the documentation, especially during transition phases.

> 1. Are there other strategies or names in your collections that are not covered here?

I personally like the “purge\_THING” standard, where this gets messy is with ordered lists rather than sets/dicts. The examples I can think of where this comes into play in AWS, we either say “This is an ordered list, items not in the list will be removed”, or AWS has already provided some form of “priority” on the sub-objects and we just expose that.

---

<div class="post-metadata">

**Author:** ![jborean](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/jborean/32/1525_2.png) [@jborean](https://forum.ansible.com/u/jborean)\
**Post date:** [October 19, 2026, 5:07am UTC](https://forum.ansible.com/t/list-options-in-modules-add-or-set-lets-talk-about-terminology/46488/4 "2026-10-19T05:07:06Z")

</div>

A common convention I’ve used in some of the Windows collections when dealing with a list of values is:

```yaml
option_to_edit:
  add: ~
  set: ~
  remove: ~

```

The values themselves would either be a string identifier or a dictionary itself if more complex but essentially entries in `add` are added if not already present, remove removes if present, and set will replace the entire value with what was specified. It might reorder depending on the resource as well.

This allows people to add, remove, or idempotently set the value based on their requirements. If they wanted to clear anything then `set: []` can be used. It also has the flexibility to add further options outside of `add`/`set`/`remove` if needed.

An example of this is [microsoft.ad.object attributes](https://docs.ansible.com/projects/ansible/latest/collections/microsoft/ad/object_module.html#parameter-attributes). The [microsoft.ad.user delegates](https://docs.ansible.com/projects/ansible/latest/collections/microsoft/ad/user_module.html#parameter-delegates) option shows how you can expand the behaviour of such an option through `lookup_failure_action` same with [microsoft.ad.user groups](https://docs.ansible.com/projects/ansible/latest/collections/microsoft/ad/user_module.html#parameter-groups). Finally [microsoft.ad.user spn](https://docs.ansible.com/projects/ansible/latest/collections/microsoft/ad/user_module.html#parameter-spn) is just a simple list of strings instead of a list of complex values for more simpler options.

This certainly isn’t perfect but it seemed quite flexible over things I’ve used in the past like `dependencies` and `dependency_action` for [ansible.windows.win\_service](https://docs.ansible.com/projects/ansible/latest/collections/ansible/windows/win_service_module.html#parameter-dependencies). The downside of this approach is I can’t combine add/remove operations together but the upside is a flatter option declaration.
