# More ways to set variables for a scope

**URL:** <https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433>\
**Category:** Project Discussions\
**Tags:** variables\
**Created:** [September 7, 2025, 2:48pm UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433 "2025-09-07T14:48:46Z")\
**Posts on this page:** 8\
**Page:** 1

<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:** [September 7, 2025, 2:48pm UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/1 "2025-09-07T14:48:47Z")

</div>

Right now, there are two ways to set variables in Ansible inside a role/playbook:

1. Use the `vars` keyword. This creates a lazyily evaluated variable that’s evaluated every time it is used.
2. Use the `ansible.builtin.set_fact` action. This creates a host-level variable with a fixed value that is computed once. (With `run_once: true` the same value can be used for all hosts.) The value is set until the play ends, or it is overridden by a subsequent `ansible.builtin.set_fact` action.

(There’s also the `ansible.builtin.include_vars` plugin, which allows to load lazily evaluated variables from a YAML or JSON file, which are then defined until the end of the play. And then roles can define variables that still exist after the role finished, depending on the setting of [DEFAULT\_PRIVATE\_ROLE\_VARS](https://docs.ansible.com/ansible/devel/reference_appendices/config.html#default-private-role-vars).)

It would be great to have a mix between `vars` and `ansible.bulitin.set_fact`, which allows define a host-level variable whose value is computed once, and which is only valid for a specific scope. This is very helpful if you for example want to set a variable to a secret token that is loaded from some secret store, and you don’t want one API request to the secret store for every single use of that variable. (And you don’t want the token to be set anymore once the scope exits.)

Right now you have to use `ansible.builtin.set_fact` twice together with [a `block` and `always`](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html#id3) (set the variable to an empty string in the `always`, for example, to “clear” it), which is both ugly and leaks the variable name to later tasks.

Another thing that would be great would be to have a way to include variables from a JSON/YAML file (or a SOPS encrypted file, so it would be great if collection plugins could do this as well) only during a scope. So basically `ansible.builtin.include_vars`, but the variables stop being around at the end of the scope (likely a `block`).

I don’t have a good suggestion how this should look - I mainly want to trigger a discussion, which hopefully spawns a proposal. So: what do you think? (I’ve been thinking about this more than once, and got reminded while writing in [How to retrieve fact with a dynamic fact name?](https://forum.ansible.com/t/44431) .)

---

<div class="post-metadata">

**Author:** ![utoddl](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/utoddl/32/249_2.png) [@utoddl](https://forum.ansible.com/u/utoddl)\
**Post date:** [September 8, 2025, 12:01am UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/2 "2025-09-08T00:01:39Z")

</div>

Off the top of my head:

- I can imagine some sort of introspection mechanism to determine the precedence of a variable. (Perhaps an option of `defined`? I guess that would make it a filter rather than a test though. Hm.)

- What about a variation of `set_fact` that – in addition to selecting from a subset of scope/precedence –allowed choosing whether the variable created was static or lazily evaluated.

- It seems like anything that can be set with `set_fact` should also be `un-set_fact`-ible. Including ~~persistent~~ _cacheable_ facts. Perhaps it should even be possible to “undefine” other variables, or mask them so they look undefined within specific scopes.

Flip side: Let’s not implement anything merely because we can. There should be a plausible use case for such changes. Let’s not make the implementers/maintainers’ jobs harder because it seemed like a cool idea in some thread way back in 2025. But I think at one time or another I’ve wanted each of these features. Obviously I’ve found ways to live without. There is a balance to be struck.

---

<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:** [September 8, 2025, 5:45am UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/3 "2025-09-08T05:45:28Z")

</div>

Trying to think of requirements here, I read two different types of scopes we might want to address (first shot at names, gladly taking better suggestions):

- target scope: whether it would be all, groups or specific hosts
- code/locality scope: playbook, play, task, role, file, maybe other locations

Anyways, this spells changes in ansible-core, which might be hard to achieve and maintain backward compatibility. Not holding my breath.

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [September 8, 2025, 2:21pm UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/4 "2025-09-08T14:21:34Z")

</div>

Ansible has 3 scopes:

- extra vars: global and overrides both other scopes
- play object: attached to the play object and inheritable and overridable by contained play objects
- host: attached to the host for the duration of the run

I attempted a simpler implementation of something close to what you want:

```auto
vars:
    x: !static {{ ... }}

```

Which would compute the variable the first time it was encountered and cache the result, but this has many issues, like it could yield very different and undesired results depending on if you wanted this per host or per play object and where the scope variables are sourced from, which the templating engine did not know or care.

The 2.19 templating revamp can help with this a bit, but I don’t believe it will resolve all the issues.  
This probably requires a redesign of how variables work in Ansible, see [redesign variable interface · Issue #127 · ansible/proposals · GitHub](https://github.com/ansible/proposals/issues/127) for one such fever dream.

---

<div class="post-metadata">

**Author:** ![sivel](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/sivel/32/1198_2.png) [@sivel](https://forum.ansible.com/u/sivel)\
**Post date:** [September 8, 2025, 6:32pm UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/5 "2025-09-08T18:32:15Z")

</div>

I don’t have any direct answers, other than to say we want to basically rewrite `VariableManager` to utilize a `ChainMap` or some derivative of it, and I think until we can get around to re-architecting this functionality, I don’t see us making any changes to variable scoping and precedence.

One place something like this would help is if we had a mechanism to support the coercing functionality for play/role argspecs. Right now the argspec validator can only assert that it _ **could** _ be coerced to the right type, but cannot actually coerce it to that type for future use. It’s undecided if we will go down this path, but if we did, we’d need a mechanism to replace the variable at it’s current level, so as to not break precedence elsewhere, and we don’t have anything to permit that currently.

---

<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:** [September 13, 2025, 8:43am UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/6 "2025-09-13T08:43:09Z")

</div>

> [@sivel](#):
>
> I don’t have any direct answers, other than to say we want to basically rewrite `VariableManager` to utilize a `ChainMap` or some derivative of it, and I think until we can get around to re-architecting this functionality, I don’t see us making any changes to variable scoping and precedence.

I didn’t say it is easy, and should be around soonish 🙂 Out of curiosity, how do `vars` (which can appear all over the place in playbooks, roles, task files, blocks, tasks) work? I would imagine they use something similar to `ChainMap`. (Which until your post I wasn’t aware of that Python had this data structure. Good to know, might become handy at some point…)

> [@sivel](#):
>
> One place something like this would help is if we had a mechanism to support the coercing functionality for play/role argspecs. Right now the argspec validator can only assert that it _ **could** _ be coerced to the right type, but cannot actually coerce it to that type for future use. It’s undecided if we will go down this path, but if we did, we’d need a mechanism to replace the variable at it’s current level, so as to not break precedence elsewhere, and we don’t have anything to permit that currently.

That would really be awesome! One of the largest drawbacks of role argument spec validation right now is that values are not coerced to the right type, it’s just checked that they can be converted to them… (And sometimes you don’t even have an official way to convert them yourself, like a filter `ansible.builtin.convert_to` or something like that.)

---

<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:** [September 13, 2025, 8:45am UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/7 "2025-09-13T08:45:43Z")

</div>

> [@russoz](#):
>
> Trying to think of requirements here, I read two different types of scopes we might want to address (first shot at names, gladly taking better suggestions):
> 
> - target scope: whether it would be all, groups or specific hosts
> - code/locality scope: playbook, play, task, role, file, maybe other locations

This is a very good point, I was mainly thinking about code/locality scope, not about target scope.

> [@russoz](#):
>
> Anyways, this spells changes in ansible-core, which might be hard to achieve and maintain backward compatibility. Not holding my breath.

Indeed, but if it’s never added because it can’t be used right now, then we also won’t be able to use it in 10 years. But if we add it now, eventually we will be able to use it (without breaking too many users, when sharing roles/playbooks).

---

<div class="post-metadata">

**Author:** ![bcoca](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/bcoca/32/9269_2.png) [@bcoca](https://forum.ansible.com/u/bcoca)\
**Post date:** [September 15, 2025, 1:19pm UTC](https://forum.ansible.com/t/more-ways-to-set-variables-for-a-scope/44433/8 "2025-09-15T13:19:23Z")

</div>

> [@felixfontein](#):
>
> I didn’t say it is easy, and should be around soonish 🙂 Out of curiosity, how do `vars` (which can appear all over the place in playbooks, roles, task files, blocks, tasks) work? I would imagine they use something similar to `ChainMap`. (Which until your post I wasn’t aware of that Python had this data structure. Good to know, might become handy at some point…)

The way `vars` currently works is as a `FieldAttribute` (`dict` type). FieldAttributes are how we implement `keywords`, they are always attached to the playbook object in which they were defined in. Contained playbook objects inherit the keywords/fields from it’s parents and merge them according to simple rules defined by the `FieldAttribute`, in the case of `vars` it is a simple dictionary merge, effectively the same as running the following Python `task_vars = play.vars.update(block.vars.update(task.vars))` in the simplest case, adding to the statement as you go down the hierarchy, collapsing into single dictionary which is then merged into the large list of sources for `VariableManager` in the ‘right’ place in it’s precedence list.

The ‘automatic type conversion’ is something that exists for keywords, config (plugins and global) and module options … in 3 different implementation of type validation + coercion systems (why I named the config one `ensure_type` instead of `validate_x` as the others. For role argument specs we wanted to avoid many issues we have hit over time with this feature as it did allow things to 'just work’TM … but not always to the intention of the user, even when the data supplied was somewhat incorrect or ‘wrongish’. Yet it seems a lot of users did appreciate this ‘magic’ and prefer being ‘slightly wrong’ vs having to fix/update/coerce their inputs.

As for filters, Jinja already has `convert_to`, one for each type: ` myvar|int`, `myvar|list`, `myvar|string`, etc.
