Introduction
The ternary filter is Ansible's one-line if-else — choose between two values based on a condition without verbose when blocks or multi-line Jinja2 {% if %} statements. But many users hit the template error: unexpected character error because they use JavaScript-style syntax. Here's the correct way.
The Wrong Way (JavaScript-style)
{# WRONG — Jinja2 doesn't support this syntax #}
{{ env == 'dev' ? 'localhost' : 'db.prod.internal' }}
Error:
template error while templating string: unexpected character
The Correct Way: Ternary Filter
db_host: "{{ (env == 'dev') | ternary('localhost', 'db.prod.internal') }}"
Syntax
{{ (condition) | ternary(true_value, false_value) }}
Three rules:
- Wrap the condition in parentheses
- Use the pipe
|beforeternary ternaryis a filter, not a function
Ternary vs Inline If-Else
Jinja2 also supports inline if-else:
# Ternary filter
db_host: "{{ (env == 'dev') | ternary('localhost', 'db.prod.internal') }}"
# Jinja2 inline if-else (also valid)
db_host: "{{ 'localhost' if env == 'dev' else 'db.prod.internal' }}"
Both are equivalent. The ternary filter is better when chaining with other filters:
# Chain-friendly
service: "{{ use_ssl | bool | ternary('https', 'http') }}"
# Inline if-else equivalent (more verbose)
service: "{{ 'https' if use_ssl | bool else 'http' }}"
Ternary with Three Values (Null/Undefined)
The ternary filter accepts an optional third argument for None/undefined:
# Returns 'default_mode' if debug is None or undefined
mode: "{{ debug | ternary('verbose', 'quiet', 'default_mode') }}"
debug value | Result |
|---|---|
true | verbose |
false | quiet |
None / undefined | default_mode |
Common Patterns
Boolean Variables
# Always use | bool for variables that might be strings
ssl_redirect: "{{ enable_ssl | bool | ternary('on', 'off') }}"
Default + Ternary (Undefined-Safe)
# Handle undefined variables
log_level: "{{ (verbose | default(false)) | bool | ternary('debug', 'info') }}"
Nested Ternary
# Multi-level selection
env_name: "{{ (is_prod | bool) | ternary('production', (is_staging | bool) | ternary('staging', 'development')) }}"
Select from Mapping (Alternative to Nested Ternary)
# Cleaner than deeply nested ternary
env_name: "{{ {'prod': 'production', 'staging': 'staging', 'dev': 'development'}[env] | default('development') }}"
In Templates (.j2 files)
server {
listen {{ (enable_ssl | bool) | ternary('443 ssl', '80') }};
server_name {{ domain }};
root {{ (env == 'dev') | ternary('/var/www/dev', '/var/www/html') }};
}
With set_fact
- name: Set database connection
ansible.builtin.set_fact:
db_host: "{{ (env == 'production') | ternary('db.prod.internal', 'localhost') }}"
db_port: "{{ (use_ssl | default(false) | bool) | ternary(5433, 5432) }}"
db_pool: "{{ (env == 'production') | ternary(20, 5) }}"
In Task Parameters
- name: Configure service
ansible.builtin.systemd:
name: myapp
state: "{{ (maintenance_mode | default(false) | bool) | ternary('stopped', 'started') }}"
enabled: "{{ (env == 'production') | ternary(true, false) }}"
Common Mistakes
| Mistake | Fix |
|---|---|
env == 'dev' ? 'a' : 'b' | (env == 'dev') | ternary('a', 'b') |
| Missing parentheses around condition | Always wrap: (condition) | ternary(...) |
Missing pipe | before ternary | ternary is a filter: | ternary(...) |
| Unquoted Jinja2 expression | Always quote: "{{ ... }}" |
Comparing bool to string 'true' | Use | bool filter first |
Debugging
- name: Debug ternary expression
ansible.builtin.debug:
msg: |
Condition value: {{ env }}
Condition type: {{ env | type_debug }}
Result: {{ (env == 'dev') | ternary('localhost', 'db.prod.internal') }}
Related Articles
- Jinja2 Conditionals in Ansible
- Ansible Assert in Jinja Templates
- Avoid Variable Collisions with loop_control
- Ansible Debug Module Guide
- Ansible Best Practices Guide
Conclusion
The ternary filter syntax is {{ (condition) | ternary(true_val, false_val) }}. Always use parentheses around the condition, pipe | before ternary, and | bool when working with variables that might be strings. For undefined-safe expressions, chain | default(false) | bool | ternary(...). Never use JavaScript-style ? : syntax — Jinja2 doesn't support it.