Template Engine

Context Variables

Context variables provide access to data from various sources throughout your Compozy workflows. Understanding the context structure and data access patterns is essential for building dynamic, data-driven configurations.

Overview

Compozy's template engine automatically provides a hierarchical context structure that gives you access to:

Workflow Data

Access input parameters, metadata, and execution state

Task Outputs

Use results from previously executed tasks

Environment Variables

System configuration and runtime settings

Collection Context

Iteration variables for collection tasks

Tool & Agent Data

Available resources and their execution state

Global Values

Shared data across workflows and tasks

Default Context Structure

Compozy automatically initializes these context fields, ensuring they're always available in your templates:

Default Context Structure
context:
  env: {}        # Environment variables map
  input: {}      # Workflow input data
  output: null   # Current task output (null by default)
  trigger: {}    # Workflow trigger context
  tools: {}      # Available tools registry
  tasks: {}      # Task execution results
  agents: {}     # Agent execution state

Core Context Variables

Workflow Context provides access to workflow-level data and metadata:

Workflow Context
# Workflow input data
user_id: "{{ .workflow.input.user_id }}"
search_query: "{{ .workflow.input.query }}"
preferences: "{{ .workflow.input.user_preferences }}"

# Workflow metadata
workflow_id: "{{ .workflow.id }}"
workflow_version: "{{ .workflow.version }}"
started_at: "{{ .workflow.started_at }}"

# Workflow configuration
timeout: "{{ .workflow.config.timeout }}"
retry_policy: "{{ .workflow.config.retry_policy }}"

Common workflow patterns:

  • Use .workflow.input for user-provided data
  • Use .workflow.id for unique workflow identification
  • Use .workflow.config for execution parameters

Advanced Context Patterns

Nested Data Access enables navigation of complex data structures:

Nested Data Access
# Deep object access
user_email: "{{ .tasks.get_user.output.profile.contact.email }}"
first_tag: "{{ .tasks.get_metadata.output.tags.0 }}"

# Safe nested access with defaults
city: "{{ .tasks.get_location.output.address.city | default 'Unknown' }}"
phone: "{{ coalesce .user.contact.phone .user.profile.phone 'No phone' }}"

# Array element access
first_result: "{{ index .tasks.search.output.results 0 }}"
last_item: "{{ index .tasks.fetch.output.items (sub (len .tasks.fetch.output.items) 1) }}"

Navigation techniques:

  • Use dot notation for nested objects
  • Use .0 or index for array access
  • Use default for safe fallbacks
  • Use coalesce for multiple fallback options

Context in Task Types

Basic Tasks use context for simple operations:

Basic Task Context
- id: api_request
  type: basic
  $use: tool(local::tools.#(id=="http_client"))
  with:
    # Workflow input
    url: "{{ .workflow.input.api_endpoint }}"

    # Environment configuration
    api_key: "{{ .env.API_KEY }}"

    # Previous task output
    user_id: "{{ .tasks.authenticate.output.user_id }}"

    # Dynamic headers
    headers:
      Authorization: "Bearer {{ .tasks.authenticate.output.token }}"
      Content-Type: "application/json"
      X-Request-ID: "{{ .workflow.id }}"

Best Practices

1

Use Meaningful Variable Names

Always use descriptive names that clearly indicate what data you're accessing:

# ✅ Good - descriptive access
user_email: "{{ .tasks.get_user_profile.output.email }}"
auth_token: "{{ .tasks.authenticate_user.output.access_token }}"

# ❌ Avoid - unclear references
data: "{{ .tasks.task1.output.data }}"
result: "{{ .tasks.step2.output }}"
2

Provide Fallback Values

Always provide sensible defaults to prevent template errors:

# Always provide sensible defaults
timeout: "{{ .workflow.config.timeout | default 30 }}"
retries: "{{ .env.MAX_RETRIES | default 3 | int }}"
user_name: "{{ .tasks.get_user.output.name | default 'Guest' }}"
3

Validate Context Before Use

Check data existence before accessing nested structures:

# Check existence before accessing nested data
safe_access: |
  {{- if and .tasks.api_call.output (hasKey .tasks.api_call.output "data") -}}
    {{ .tasks.api_call.output.data.result }}
  {{- else -}}
    null
  {{- end -}}
4

Use Type-Appropriate Conversions

Convert data types as needed for proper processing:

# Convert types as needed
port_number: "{{ .env.PORT | default '3000' | int }}"
debug_flag: "{{ .env.DEBUG | default 'false' | bool }}"
timeout_float: "{{ .config.timeout | default '30.5' | float64 }}"
5

Document Complex Context Usage

Add comments to explain complex context access patterns:

# Document complex context access patterns
user_notification_config:
  # User's preferred notification method from profile
  method: "{{ .tasks.get_user_profile.output.notifications.method | default 'email' }}"

  # Fallback to workflow input if profile incomplete
  email: "{{ coalesce .tasks.get_user_profile.output.email .workflow.input.email }}"

  # Environment-based configuration
  service_url: "{{ .env.NOTIFICATION_SERVICE_URL }}"

  # Dynamic content based on user type
  template: |
    {{- if eq .tasks.get_user_profile.output.type "premium" -}}
      premium_notification
    {{- else -}}
      standard_notification
    {{- end -}}

References

Context variables are the foundation of dynamic Compozy workflows. Understanding how to access and manipulate this data is essential for building powerful, data-driven templates.