# default variables in modular task files

**URL:** <https://forum.ansible.com/t/default-variables-in-modular-task-files/46516>\
**Category:** Get Help\
**Tags:** include\_tasks, yaml, task, ansible-core\
**Created:** [October 23, 2026, 6:23am UTC](https://forum.ansible.com/t/default-variables-in-modular-task-files/46516 "2026-10-23T06:23:10Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![aether](https://avatars.discourse-cdn.com/v4/letter/a/8dc957/32.png) [@aether](https://forum.ansible.com/u/aether)\
**Post date:** [October 23, 2026, 6:23am UTC](https://forum.ansible.com/t/default-variables-in-modular-task-files/46516/1 "2026-10-23T06:23:10Z")

</div>

I’d like to use task files and `include_tasks` for some lightweight reusable modules (I’m open to other module solutions, but want to avoid roles when my files are \<50 lines). I’m trying to enable default values within the task files, while deferring to any outside values where possible.

Ansible’s behaviour seems to be to override task file variables with variables from the `include_tasks`, but not variables defined above:

playbook.yml

```yaml
- name: var test
  hosts: all
  vars:
    var: playbook-toplvl-default
  tasks:
   - name: Call task file
     # setting vars here would override task-file.yml, but not above
     # e.g. vars: { var: would-override }
     include_tasks: task-file.yml

```

task-files.yml

```yml
- name: task-file task
  vars:
    var: task-file-internal-default
  ansible.builtin.debug:
    msg: 'Value: {{ var }}'

```

The example above prints `Value: task-file-internal-default`, where I would like it to print `Value: playbook-toplvl-default` instead. How best would I achieve my desired effect?

Two possible solutions I’ve come up with on my own:

1. use yaml anchors to duplicate vars:

```auto
- name: var test
  hosts: all
  vars: &vars
    var: playbook-toplvl-default
  tasks:
   - name: Call task file
     vars: *vars
     include_tasks: task-file.yml

```

- this requires an extra unusual step from the module user, increasing the chance of error every time the module is used. So not ideal

1. use a different internal variable name, and explicit `default()` usage

```yaml
- name: task-file task
  vars:
    _var: '{{ var | default("task-file-internal-default") }}'
  ansible.builtin.debug:
    msg: 'Value: {{ _var }}'

```

- custom logic is self-contained and universal
- makes the module a little more messy

I’m curious to hear if any more experienced users have other solutions, or if there are pros/cons I haven’t thought about yet.

---

<div class="post-metadata">

**Author:** ![kks](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/kks/32/10352_2.png) [@kks](https://forum.ansible.com/u/kks)\
**Post date:** [October 23, 2026, 7:17am UTC](https://forum.ansible.com/t/default-variables-in-modular-task-files/46516/2 "2026-10-23T07:17:41Z")

</div>

> [@aether](#):
>
> I’m open to other module solutions

You do not have to create a complete role, if you want to have reuse only for particular exising role. But if you want reuse in other roles you will have to create separate role. You will just need role tasks/main.yml file and defaults/main.yml for defaults and put your “private” role in ./roles folder near tasks file you import/include that role

> [@aether](#):
>
> deferring to any outside values where possible.

This is exacly what defaults/main.yml in your “private” role will do.

Create your role in ./roles folder in ./roles//tasks folder and include it (or import it).  
When you import / include the role from your public role tasks file, all the variable from “private” role defaults will be overwritten by any other variable defined at the moment of import.

```yaml
- include_role:
  name: <your private role, no FQDN>

```

ansible will automatically look for role in ./roles in your public role tasks folder.

---

<div class="post-metadata">

**Author:** ![aether](https://avatars.discourse-cdn.com/v4/letter/a/8dc957/32.png) [@aether](https://forum.ansible.com/u/aether)\
**Post date:** [October 23, 2026, 7:28am UTC](https://forum.ansible.com/t/default-variables-in-modular-task-files/46516/3 "2026-10-23T07:28:31Z")

</div>

After further thought, I think allowing _all_ upstream variables to override, like I had with solution 2., is more dangerous than the alternatives. If someone upstream sets the variable `state` to `absent`, that’s probably going to have a big impact on the behaviour of downstream modules without necessarily intending it. I should be keeping scope tight by requiring variables to be set next to the call.

With that in mind, this is the solution I’ve come up with:

playbook.yaml:

```yaml
- name: include task test
  hosts: all
  vars:
    _required_var: required
    _var: toplvl-default
  tasks:
    - name: include task
      include_tasks: task-file.yml
      vars:
        # renaming variable required because `var: {{ var }}` causes an infinite loop
        var: '{{ _var }}'
        required_var: '{{ _required_var }}'

```

task-file.yaml:

```yaml
- name: included task
  vars:
    required_var: null
    var: task-file-internal-default

  block:
    # might not be necessary if other tasks in this block do variable validation instead
    - name: variable validation
      assert:
        that:
          - required_var != None
        fail_msg: |-
          Missing required variables:
          required_var: {{ required_var | default('UNDEFINED', true) }}

    - name: print values
      ansible.builtin.debug:
        msg: 'Value: {{ var }} and {{ required_var }}'

```

By setting `required_var: null`, we ensure that if `required_var` is not explicitly set next to `include_tasks`, it doesn’t default to some other lower precedence value of `required_var`. That way we can keep the variable properly ‘required’.
