Templates
Dynamic values using Handlebars templating.
Overview
Templates let you insert dynamic PR data into action values. They use Handlebars syntax with custom helpers for text processing, extraction, and user mapping.
then:
update_fields:
'1234567890': 'PR #{{pr.number}}: {{pr.title}}'
'1111111111': '{{pr.author}}'Basic Syntax
Variable Interpolation
title: '{{pr.title}}'
# Result: "Add user authentication"
notes: 'PR by {{pr.author}}'
# Result: "PR by octocat"Nested Properties
notes: 'Event: {{event.name}} - {{event.action}}'
# Result: "Event: pull_request - opened"Context Variables
PR Data
Available in all templates:
Event Data
Label Data
Only available when label condition matches:
Task Data
Only available in post_pr_comment:
See Context Variables reference.
Handlebars Helpers
Text Processing
clean_title
Remove conventional commit prefixes:
# Input: "feat: Add dark mode"
# Output: "Add dark mode"
# Input: "chore(deps): bump lodash"
# Output: "bump lodash"sanitize_markdown
Convert markdown to Asana-safe HTML:
Handles: headers, lists, code blocks, links, images.
See sanitize_markdown reference.
Extraction
extract_from_body
Extract text from PR body using regex:
# PR body: "Fixes TICKET-1234"
# Result: "1234"Returns first capture group or empty string.
See extract_from_body reference.
extract_from_title
Extract from PR title:
# PR title: "[URGENT] Fix auth bug"
# Result: "URGENT"See extract_from_title reference.
extract_from_comments
Extract from PR comments:
# Comment: "Build #12345 completed"
# Result: "12345"Comment Fetching
Using this helper automatically fetches PR comments via GitHub API. This is the only time comments are fetched to avoid unnecessary API calls.
See extract_from_comments reference.
User Mapping
map_github_to_asana
Map GitHub username to Asana user GID:
Requires user_mappings input:
user_mappings: |
octocat: 1234567890
alice: 0987654321Returns empty string if not mapped.
See map_github_to_asana reference.
Utilities
or
Logical OR / fallback values:
Returns first truthy value:
assignee: '{{or pr.assignee pr.author}}'
# If pr.assignee exists, use it
# Otherwise use pr.authorfield: '{{or (extract_from_body "TICKET-\\d+") "No ticket"}}'
# If extraction succeeds, use it
# Otherwise use "No ticket"See or helper reference.
Block Helpers
#if / #unless
Conditional content:
#each
Iterate over arrays (mainly for tasks in comments):
Common Patterns
Task Titles
title: '{{clean_title pr.title}}'
# Clean commit prefixes
title: '{{pr.title}} #{{pr.number}}'
# Include PR number
title: '{{extract_from_title "\\[([A-Z]+)\\]"}} {{clean_title pr.title}}'
# Extract tag + clean titleTask Notes
notes: |
PR: {{pr.url}}
Author: {{pr.author}}
{{#if pr.assignee}}Assignee: {{pr.assignee}}{{/if}}html_notes: |
<strong>PR:</strong> <a href="{{pr.url}}">{{pr.title}}</a>
<br><br>
{{sanitize_markdown pr.body}}Field Values
update_fields:
# Author
'1111': '{{pr.author}}'
# Ticket number
'2222': '{{extract_from_body "TICKET-(\\d+)"}}'
# Build number
'3333': '{{extract_from_comments "Build #(\\d+)"}}'
# PR link
'4444': '{{pr.url}}'Assignment
# Prefer assignee, fallback to author
assignee: '{{or (map_github_to_asana pr.assignee) (map_github_to_asana pr.author)}}'
# Map author with default
assignee: '{{or (map_github_to_asana pr.author) "1234567890"}}'
# Just author
assignee: '{{map_github_to_asana pr.author}}'PR Comments
post_pr_comment: |
✅ Updated {{summary.total}} Asana task{{#unless (eq summary.total 1)}}s{{/unless}}
{{#each tasks}}
• [{{name}}]({{permalink_url}})
{{/each}}
PR: {{pr.title}} by {{pr.author}}Conditional Updates
update_fields:
'1111': '{{extract_from_body "BUILD-(\\d+)"}}'
# If pattern doesn't match, returns empty string
# Empty values skip field update automaticallyComplex Extraction
update_fields:
# Extract version
'1111': '{{extract_from_comments "Version: ([\\d.]+)"}}'
# Extract and format
'2222': 'Build {{extract_from_comments "Build #(\\d+)"}} (firebase)'
# Multiple extractions
'3333': '{{extract_from_body "ENV: (\\w+)"}} - {{extract_from_body "REGION: (\\w+)"}}'Template Evaluation
Templates are evaluated when:
- PR data is loaded
- Asana tasks are found (if
has_asana_tasks: true) - Comments are fetched (if
extract_from_commentsused)
Empty Values
Critical Behavior
Templates that evaluate to exactly an empty string ('') trigger special handling. Whitespace is NOT considered empty—only the exact empty string.
How empty values are handled:
| Context | Behavior | Example |
|---|---|---|
update_fields | Field update is skipped | ✅ Safe |
create_task.title | Empty string used | ❌ Validation fails |
create_task.notes | Empty string used | ✅ Allowed |
create_task.assignee | Empty string used | ✅ No assignee set |
create_task.initial_fields | Field update skipped | ✅ Safe |
Important: Only exact empty strings ('') are skipped. These are NOT empty:
' '(single space) → NOT skipped'\n'(newline) → NOT skipped' '(multiple spaces) → NOT skipped
Examples:
update_fields:
'1111': '{{extract_from_body "TICKET-(\\d+)"}}'
# No match → '' (empty) → field update SKIPPED ✓
update_fields:
'2222': 'Ticket: {{extract_from_body "TICKET-(\\d+)"}}'
# No match → 'Ticket: ' (NOT empty) → sets field to 'Ticket: ' ✗
create_task:
title: '{{extract_from_body "TICKET-(\\d+)"}}'
# No match → '' (empty) → validation error (title required) ✗
title: '{{or (extract_from_body "TICKET-(\\d+)") pr.title}}'
# No match → uses pr.title → safe ✓Best Practice
Use the or helper to provide fallback values:
update_fields:
'1234567890': '{{or (extract_from_body "Version: ([\\d.]+)") "Unknown"}}'This prevents empty values while maintaining graceful fallbacks.
Escaping
Handlebars is configured with noEscape: true - no HTML escaping:
html_notes: '<strong>{{pr.title}}</strong>'
# Output: <strong>Add feature</strong>
# NOT: <strong>Add feature</strong>Debugging
Use post_pr_comment to debug template values:
then:
post_pr_comment: |
Debug:
- pr.author: "{{pr.author}}"
- pr.assignee: "{{pr.assignee}}"
- Mapped: "{{map_github_to_asana pr.author}}"
- Extracted: "{{extract_from_body "TICKET-(\\d+)"}}"Validation
Templates are validated:
- Syntax errors fail rule validation
- Missing variables return empty strings (no errors)
- Helper errors return empty strings and log warnings
Next Steps
- Context Variables - All available variables
- Extraction Helpers - Pattern extraction
- Text Processing - Text manipulation
- User Mapping - GitHub to Asana mapping
- Utilities - OR helper and more