Skip to main content

What is a Workflow?

A Workflow in AWX is a structured composition of job resources that enables complex automation scenarios. Workflows execute jobs in a specific order based on success/failure paths, allowing you to chain together multiple job templates, project updates, inventory updates, and even other workflows into sophisticated automation pipelines.
Workflows transform simple automation tasks into powerful orchestration pipelines with conditional logic, parallel execution, and dependency management.

Core Concepts

Workflow Components

From the WorkflowJobTemplate model (awx/main/models/workflow.py:455-635): A workflow consists of:
  1. Workflow Job Template: The reusable definition
  2. Workflow Nodes: Individual steps in the workflow
  3. Node Relationships: Success, failure, and always paths between nodes
  4. Launch Configuration: Variables and prompts
  5. Approval Nodes: Manual approval steps

Workflow Job Templates

Key fields (workflow.py:455-467):
FieldTypeDescription
nameStringWorkflow name (unique within organization)
descriptionStringOptional description
organizationForeignKeyOrganization that owns this workflow
extra_varsTextFieldGlobal variables for all jobs in workflow
allow_simultaneousBooleanAllow concurrent workflow executions
ask_variables_on_launchBooleanPrompt for extra_vars at launch
ask_inventory_on_launchBooleanPrompt for inventory at launch
ask_limit_on_launchBooleanPrompt for limit at launch
ask_scm_branch_on_launchBooleanPrompt for SCM branch at launch
survey_enabledBooleanEnable survey for user input
survey_specJSONSurvey definition

Workflow Nodes

Workflow nodes are the building blocks of workflows (awx/main/models/workflow.py:64-235):

Node Types

A node can reference any of these unified job templates:
  • Job Template: Run an Ansible playbook
  • Project Update: Sync a project from SCM
  • Inventory Update: Sync an inventory source
  • Workflow Job Template: Nested workflow
  • Workflow Approval: Manual approval step

Node Relationships

# From workflow.py:69-86
success_nodes = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='%(class)ss_success')
failure_nodes = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='%(class)ss_failure')
always_nodes = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='%(class)ss_always')
all_parents_must_converge = models.BooleanField(
    default=False, 
    help_text=_("If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node")
)

Success Nodes

Executed when parent node succeeds

Failure Nodes

Executed when parent node fails

Always Nodes

Executed regardless of parent status

Node Convergence

The all_parents_must_converge field controls execution logic:
  • False (default): Node runs if ANY parent’s condition is met
  • True: Node runs only if ALL parents’ conditions are met
This enables complex AND/OR logic in workflow graphs.

Node Identifiers

Nodes have an identifier field for tracking:
# From workflow.py:183-188
identifier = models.CharField(
    max_length=512,
    default=uuid4,
    blank=False,
    help_text=_('An identifier for this node that is unique within its workflow. It is copied to workflow job nodes corresponding to this node.'),
)
Identifiers enable:
  • Idempotent node creation/updates
  • Tracking job nodes back to template nodes
  • Client-side workflow management

Workflow Execution

When a workflow launches, it creates a Workflow Job (awx/main/models/workflow.py:637-789):

Execution Flow

From docs/workflow.md:110:
1

Create Workflow Job

Workflow job template creates a workflow job instance
2

Create Job Nodes

Each template node creates a corresponding job node
3

Start Root Nodes

Nodes with no parents begin execution
4

Follow Paths

As jobs complete, appropriate child nodes execute based on status
5

Complete

Workflow finishes when all decision trees complete

Node Job Creation

When a node’s turn comes, it creates a job:
# From workflow.py:297-388
def get_job_kwargs(self):
    """
    In advance of creating a new unified job as part of a workflow,
    this method builds the attributes to use
    """
    data = {}
    wj_special_vars = {}
    wj_special_passwords = {}
    ujt_obj = self.unified_job_template
    if ujt_obj is not None:
        node_prompts_data = self.prompts_dict(for_cls=ujt_obj.__class__)
        wj_prompts_data = self.workflow_job.prompts_dict(for_cls=ujt_obj.__class__)
        # ...
        accepted_fields, ignored_fields, errors = ujt_obj._accept_or_ignore_job_kwargs(**node_prompts_data)
        data.update(accepted_fields)
Job parameters come from:
  1. Node-level prompts (highest priority)
  2. Workflow job prompts
  3. Template defaults (lowest priority)

Workflow Variables

Workflows have sophisticated variable handling:

Extra Variables

From docs/workflow.md:112:
Workflow extra_vars > Node extra_vars > Job Template extra_vars
Workflow-level extra variables override node and job template variables.

Artifact Passing

Jobs can pass data to downstream nodes using set_stats:
- name: Register artifact
  ansible.builtin.set_stats:
    data:
      deployment_id: "{{ deployment.id }}"
      deployment_url: "https://app.example.com/{{ deployment.id }}"
Artifacts flow through the workflow:
# From workflow.py:345-355
aa_dict = {}
is_root_node = True
for parent_node in self.get_parent_nodes():
    is_root_node = False
    aa_dict.update(parent_node.ancestor_artifacts)
    if parent_node.job:
        aa_dict.update(parent_node.job.get_effective_artifacts(parents_set=set([self.workflow_job_id])))
if aa_dict and not is_root_node:
    self.ancestor_artifacts = aa_dict
    self.save(update_fields=['ancestor_artifacts'])
Artifacts from parent nodes are merged and passed as extra_vars to child jobs.
Artifacts marked with _ansible_no_log: true have their keys replaced with $hidden due to Ansible no_log flag$ to prevent exposure of sensitive data.

Workflow Approvals

Approval nodes pause workflow execution for manual approval (awx/main/models/workflow.py:829-1017):

Creating Approval Nodes

# From workflow.py:236-241
def create_approval_template(self, **kwargs):
    approval_template = WorkflowApprovalTemplate(**kwargs)
    approval_template.save()
    self.unified_job_template = approval_template
    self.save(update_fields=['unified_job_template'])
    return approval_template

Approval Fields

FieldTypeDescription
nameStringApproval node name
descriptionStringApproval description/instructions
timeoutIntegerTimeout in seconds (0 = no timeout)
approved_or_denied_byForeignKeyUser who approved/denied
timed_outBooleanWhether approval timed out

Approval Actions

Approvals can be approved or denied:
# From workflow.py:901-917
def approve(self, request=None):
    self.status = 'successful'
    self.approved_or_denied_by = get_current_user()
    self.save()
    self.send_approval_notification('approved')
    self.websocket_emit_status(self.status)
    ScheduleWorkflowManager().schedule()
    return reverse('api:workflow_approval_approve', kwargs={'pk': self.pk}, request=request)

def deny(self, request=None):
    self.status = 'failed'
    self.approved_or_denied_by = get_current_user()
    self.save()
    self.send_approval_notification('denied')
    self.websocket_emit_status(self.status)
    ScheduleWorkflowManager().schedule()
    return reverse('api:workflow_approval_deny', kwargs={'pk': self.pk}, request=request)
Approved nodes continue to success path; denied nodes follow failure path.

Approval Permissions

From docs/workflow.md:82-93: Users can approve if they are:
  • Superuser
  • Organization Admin
  • Workflow Admin
  • Assigned the approval role on the workflow

Nested Workflows

Workflows can contain other workflows:
# From workflow.py:64-70
unified_job_template = models.ForeignKey(
    'UnifiedJobTemplate',
    related_name='%(class)ss',
    blank=True,
    null=True,
    default=None,
    on_delete=models.SET_NULL,
)
This enables:
  • Reusable workflow components
  • Complex multi-level orchestration
  • Recursion detection (AWX prevents infinite loops)
From docs/workflow.md:72-74:
In the event that spawning the workflow would result in recursion, the child workflow will be marked as failed with a message explaining that recursion was detected.

Job Slicing in Workflows

Workflows support job slicing:
# From workflow.py:382-388
if 'job_slice' in self.ancestor_artifacts and is_root_node:
    data['_eager_fields']['allow_simultaneous'] = True
    data['_eager_fields']['job_slice_number'] = self.ancestor_artifacts['job_slice']
    data['_eager_fields']['job_slice_count'] = self.workflow_job.workflow_job_nodes.count()
    data['_prevent_slicing'] = True
When a job template with job_slice_count > 1 is launched, it creates a workflow job with slice nodes.

API Endpoints

List Workflow Job Templates

GET /api/v2/workflow_job_templates/

Create Workflow Job Template

POST /api/v2/workflow_job_templates/
Content-Type: application/json

{
  "name": "Deploy Application",
  "description": "Full deployment workflow",
  "organization": 1,
  "extra_vars": "---\napp_version: 1.0.0",
  "ask_variables_on_launch": true
}

Create Workflow Node

POST /api/v2/workflow_job_templates/{id}/workflow_nodes/
Content-Type: application/json

{
  "unified_job_template": 5,
  "identifier": "deploy-backend",
  "extra_data": {
    "service": "backend"
  }
}
POST /api/v2/workflow_job_template_nodes/{id}/success_nodes/
Content-Type: application/json

{
  "id": 42
}

Create Approval Node

POST /api/v2/workflow_job_template_nodes/{id}/create_approval_template/
Content-Type: application/json

{
  "name": "Deploy to Production?",
  "description": "Approve deployment to production environment",
  "timeout": 3600
}

Launch Workflow

POST /api/v2/workflow_job_templates/{id}/launch/
Content-Type: application/json

{
  "extra_vars": {
    "app_version": "1.2.3",
    "environment": "production"
  }
}

Approve Workflow Approval

POST /api/v2/workflow_approvals/{id}/approve/

Deny Workflow Approval

POST /api/v2/workflow_approvals/{id}/deny/

Workflow Status

Workflow job status is determined by node outcomes: From docs/workflow.md:133-134:
A workflow job is marked as failed if a job spawned by a workflow job fails, without a failure handler. A failure handler is a failure or always link in the workflow job template.
Status logic:
  • Successful: All execution paths completed successfully or handled failures
  • Failed: At least one node failed without a failure handler
  • Canceled: Workflow was canceled
  • Error: System error occurred

Permissions

Workflow job templates have these roles (workflow.py:481-504):
  • Admin Role: Full control over workflow
  • Execute Role: Can launch workflows
  • Approval Role: Can approve approval nodes
  • Read Role: Can view workflow details
The approval role is separate from execute role, allowing you to designate specific approvers who may not have permission to launch workflows.

Notifications

Workflows support standard notifications plus approval notifications:
# From workflow.py:536-561
@property
def notification_templates(self):
    base_notification_templates = NotificationTemplate.objects.all()
    error_notification_templates = list(base_notification_templates.filter(unifiedjobtemplate_notification_templates_for_errors__in=[self]))
    started_notification_templates = list(base_notification_templates.filter(unifiedjobtemplate_notification_templates_for_started__in=[self]))
    success_notification_templates = list(base_notification_templates.filter(unifiedjobtemplate_notification_templates_for_success__in=[self]))
    approval_notification_templates = list(base_notification_templates.filter(workflowjobtemplate_notification_templates_for_approvals__in=[self]))
Notification types:
  • Started: Workflow job starts
  • Success: Workflow job succeeds
  • Error: Workflow job fails
  • Approvals: Approval node needs attention

Best Practices

Always add failure handlers for critical paths. Use failure nodes to send notifications, roll back changes, or trigger remediation.
Add approval nodes before critical operations (production deployments, destructive changes) to ensure human oversight.
Use set_stats to pass data between jobs. This enables dynamic workflows that adapt based on upstream results.
Create smaller, reusable workflows rather than one massive workflow. Use nested workflows for modularity.
Set all_parents_must_converge: true only when you need AND logic. The default OR logic is more flexible.
Test all paths (success, failure, always) to ensure workflows behave correctly in all scenarios.

Workflow Visualization

From docs/workflow.md:154-174, AWX provides visual workflow execution tracking:
  • Root nodes start execution
  • Node colors indicate status (pending, running, successful, failed)
  • Paths show which branches executed
  • DNR (Do Not Run) marks nodes that won’t execute

Build docs developers (and LLMs) love