"Azure DevOps CLI (az ado). Use for work items, PRs, pipelines, and backlog management. Triggers on: az ado, ADO, azure devops, work item, backlog, az boards, az repos, az pipelines."
Install
npx skillscat add ianphil/my-skills/ado Install via the SkillsCat registry.
ado - Azure DevOps CLI
Use az devops (plus az boards, az repos, az pipelines) from PowerShell. Assumes az login and org/project defaults.
Configuration
If ado\config.json is missing, ask:
- What is your ADO organization URL? (e.g.,
https://dev.azure.com/myorg) - What is your project name?
- What area path should Epics use?
- What area path should Features/Stories/Tasks/Bugs use?
- What iteration path should work items use?
Save to ado\config.json (gitignored). Config shape:
{
"organization": "https://dev.azure.com/ORG",
"project": "PROJECT",
"areaPaths": {
"epic": "Project\\Team",
"feature": "Project\\Team\\SubTeam",
"story": "Project\\Team\\SubTeam",
"task": "Project\\Team\\SubTeam",
"bug": "Project\\Team\\SubTeam"
},
"iterationPath": "Project\\Iteration",
"storyPointScale": [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
}Load config:
$config = Get-Content ".\ado\config.json" | ConvertFrom-JsonPrerequisites
Verify auth and defaults:
az account show --query "{name:name, user:user.name}" -o table
az devops configure --listIf defaults are missing:
az devops configure --defaults organization=$config.organization project=$config.projectRules
- Always use PowerShell for scripting and JSON; do not pipe to python/node.
- Use
--queryJMESPath onazcommands to filter JSON before PowerShell when possible. - For any multi-step operation, read the matching section below first; never guess
azflags or parameters.
State Transitions
User Story lifecycle
New -> Ready to Review -> Ready to Code -> Active -> Closed
| State | Meaning |
|---|---|
| New | Just created, not triaged |
| Ready to Review | Discuss in planning |
| Ready to Code | Planned and estimated |
| Active | In progress |
| Closed | Done |
| Removed | Deleted/cancelled |
Feature and Epic lifecycle
New -> Active -> Closed
Transition commands
az boards work-item update --id ID --state "Ready to Review"
az boards work-item update --id ID --state "Ready to Code"
az boards work-item update --id ID --state Active
az boards work-item update --id ID --state ClosedQuick Reference
Essential commands. For multi-step workflows, read the matching section in "Section Router" first.
# Show a work item
az boards work-item show --id ID --output table
# Show with relations (children, parent, related)
az boards work-item show --id ID --expand relations --output json
# Get child IDs from a parent (e.g., features under an epic)
$json = az boards work-item show --id PARENT_ID --expand relations --output json | ConvertFrom-Json
$childIds = $json.relations | Where-Object { $_.attributes.name -eq 'Child' } | ForEach-Object { $_.url -replace '.*/', '' }
# Show multiple work items (loop - there is no --ids flag)
$childIds | ForEach-Object { az boards work-item show --id $_ --output json } | ForEach-Object { $_ | ConvertFrom-Json } | Select-Object id, @{N='Type';E={$_.fields.'System.WorkItemType'}}, @{N='Title';E={$_.fields.'System.Title'}}, @{N='State';E={$_.fields.'System.State'}} | Format-Table
# Query work items with WIQL
az boards query --wiql "SELECT [System.Id], [System.Title], [System.State] FROM WorkItems WHERE [System.WorkItemType] = 'User Story' AND [System.State] = 'Active'" --output table
# Create a work item
az boards work-item create --type "User Story" --title "Title" --area $config.areaPaths.story --iteration $config.iterationPath
# Link child to parent (no --parent flag exists)
az boards work-item relation add --id CHILD_ID --relation-type parent --target-id PARENT_ID
# Update state
az boards work-item update --id ID --state Active
# Create a PR linked to work item
az repos pr create --title "feat: description" --work-items ID --auto-complete true --delete-source-branch true
# List active PRs
az repos pr list --status active --output table
# List pipeline runs
az pipelines runs list --top 5 --output tableSection Router
| User intent | Section | Read before |
|---|---|---|
| Pick up work items, create branches, make PRs, monitor builds | Dev Flow | Any PR or branch workflow |
| Create features/stories, hierarchy, area paths, estimation | Planning Flow | Creating or linking work items |
| WIP, aging, throughput, cycle time, Kanban health | Backlog Management | Any backlog query or metric |
| Pipeline status, failed builds, logs, triggers | Pipeline Debugging | Any pipeline operation |
| WIQL field names, operators, macros, quoting | WIQL Reference | Writing any WIQL query |
| CLI command patterns, bulk ops, REST API, output formatting | Command Cookbook | Bulk operations or REST API calls |
| Board columns, WIP limits, Kanban column queries | Board Columns API | Any board column query |
Troubleshooting
| Problem | Fix |
|---|---|
az account show fails |
Re-run az login - token expired |
| Empty query results | Check area path spelling; for FROM WorkItemLinks use REST API instead |
charmap encoding error |
Use az devops invoke not az rest; set $env:PYTHONIOENCODING = "utf-8" |
| Defaults not set | Run az devops configure --defaults organization=$config.organization project=$config.project |
| Permission denied on work items | Verify area path permissions in ADO project settings |
| WIQL syntax error | Check quoting; see WIQL Reference |
Additional Tips
- Use
--output tablefor readable output,--output jsonfor parsing - Use
--querywith JMESPath to filter JSON:--query "[].{Id:id, Title:fields.\"System.Title\"}" - WIQL supports
@Me,@Today,@Today - Nmacros - Link PRs to work items with
AB#IDin commits or PR descriptions - Use
az devops wikifor wiki operations - Use
az boards iterationandaz boards areato manage team structure
Dev Flow - Work Item -> Branch -> PR -> Merge
Use for picking up work items, creating branches, making PRs, or monitoring PR pipelines.
1. Pick up a work item
# Find items ready to work on
az boards query --wiql "SELECT [System.Id], [System.Title], [System.State] `
FROM WorkItems `
WHERE [System.AssignedTo] = @Me `
AND [System.State] = 'Ready to Code' `
ORDER BY [Microsoft.VSTS.Common.Priority]" --output table
# Activate the item
az boards work-item update --id ID --state Active2. Create a branch
git checkout -b feature/ID-short-description3. Create a PR linked to work item
az repos pr create `
--title "feat: description" `
--description "Resolves AB#ID" `
--work-items ID `
--auto-complete true `
--delete-source-branch trueThe --work-items flag links the PR. Use AB#ID in description for extra linking.
4. Monitor pipeline on PR
# List runs for the PR branch
az pipelines runs list --branch feature/ID-short-description --top 1 --output table
# Check run details
az pipelines runs show --id RUN_ID --output table5. Complete PR
az repos pr update --id PR_ID --status completedWork item state transitions automatically if board rules are configured.
Assign to self
$me = az account show --query "user.name" -o tsv
az boards work-item update --id ID --assigned-to $mePlanning Flow - Features, Stories, Hierarchy and Estimation
Assumes $config loaded from .\ado\config.json.
Path conventions
| Work Item Type | Area Path (config key) | Iteration Path (config key) |
|---|---|---|
| Epic | areaPaths.epic |
iterationPath |
| Feature | areaPaths.feature |
iterationPath |
| User Story | areaPaths.story |
iterationPath |
| Task | areaPaths.task |
iterationPath |
| Bug | areaPaths.bug |
iterationPath |
Descriptions and acceptance criteria
| Work Item Type | Description Field | Acceptance Criteria |
|---|---|---|
| Feature | --description (put AC here) |
N/A - use description |
| User Story | --description (user story text) |
--fields "Microsoft.VSTS.Common.AcceptanceCriteria=..." |
Both fields accept HTML. Use <ul><li> for bullet lists:
# Feature - AC goes in description
az boards work-item update --id FEATURE_ID `
--description "<h3>Acceptance Criteria</h3><ul><li>Criterion 1</li><li>Criterion 2</li></ul>"
# Story - description for story, AC in its own field
az boards work-item update --id STORY_ID `
--description "As a user, I want to..." `
--fields "Microsoft.VSTS.Common.AcceptanceCriteria=<ul><li>Criterion 1</li><li>Criterion 2</li></ul>"Create a Feature under an Epic
$featureId = az boards work-item create `
--type Feature `
--title "Feature title" `
--description "Feature description" `
--area $config.areaPaths.feature `
--iteration $config.iterationPath `
--query "id" -o tsv
az boards work-item relation add --id $featureId --relation-type parent --target-id EPIC_IDCreate Stories under a Feature
$storyId = az boards work-item create `
--type "User Story" `
--title "Story title" `
--description "As a user, I want..." `
--area $config.areaPaths.story `
--iteration $config.iterationPath `
--query "id" -o tsv
az boards work-item relation add --id $storyId --relation-type parent --target-id FEATURE_IDBulk story creation
$featureId = FEATURE_ID
@("Story A", "Story B", "Story C") | ForEach-Object {
$storyId = az boards work-item create --type "User Story" --title $_ `
--area $config.areaPaths.story `
--iteration $config.iterationPath `
--query "id" -o tsv
az boards work-item relation add --id $storyId --relation-type parent --target-id $featureId
}Set iteration and area paths
az boards work-item update --id ID `
--area $config.areaPaths.story `
--iteration $config.iterationPathEstimation (Story Points)
Story points use the scale from config.json (default: first 10 Fibonacci numbers).
az boards work-item update --id ID `
--fields "Microsoft.VSTS.Scheduling.StoryPoints=5"Bulk estimate stories
$estimates = @{
12345 = 3
12346 = 8
12347 = 5
}
$estimates.GetEnumerator() | ForEach-Object {
az boards work-item update --id $_.Key `
--fields "Microsoft.VSTS.Scheduling.StoryPoints=$($_.Value)" --output table
}Complexity guide
| Points | Complexity |
|---|---|
| 1 | Trivial - config change, typo fix |
| 2 | Small - single file, well-understood change |
| 3 | Small-medium - a few files, straightforward |
| 5 | Medium - multiple files, some design needed |
| 8 | Medium-large - cross-component, some unknowns |
| 13 | Large - significant effort, multiple components |
| 21 | Very large - consider splitting |
| 34 | Epic-sized - must be split |
| 55 | Program-level - break into features |
Backlog Management - Kanban Metrics and Queries
For WIQL syntax, see WIQL Reference. For board columns and WIP limits, see Board Columns API.
WIP - Items in progress
az boards query --wiql "SELECT [System.Id], [System.Title], [System.AssignedTo] `
FROM WorkItems `
WHERE [System.State] = 'Active' `
AND [System.WorkItemType] = 'User Story' `
ORDER BY [System.ChangedDate]" --output tableAging - Items stale in a state
Find items stuck in Active for more than 7 days:
az boards query --wiql "SELECT [System.Id], [System.Title], [System.ChangedDate] `
FROM WorkItems `
WHERE [System.State] = 'Active' `
AND [System.ChangedDate] < @Today - 7 `
AND [System.WorkItemType] = 'User Story'" --output tableThroughput - Recently completed
Items closed in the last 14 days:
az boards query --wiql "SELECT [System.Id], [System.Title], [System.ChangedDate] `
FROM WorkItems `
WHERE [System.State] = 'Closed' `
AND [System.ChangedDate] >= @Today - 14 `
AND [System.WorkItemType] = 'User Story' `
ORDER BY [System.ChangedDate] DESC" --output tableState distribution
Count items per state:
$items = az boards query --wiql "SELECT [System.Id], [System.State] `
FROM WorkItems `
WHERE [System.WorkItemType] = 'User Story' `
AND [System.State] <> 'Removed'" --output json | ConvertFrom-Json
$items | Group-Object { $_.fields.'System.State' } |
Select-Object @{N='State';E={$_.Name}}, Count |
Sort-Object Count -Descending | Format-TableCycle time
Query item revision history (REST):
az devops invoke `
--area wit `
--resource revisions `
--route-parameters project=$config.project workItemId=ID `
--api-version 7.0Pipeline Debugging - Runs, Logs and Triggers
Use for pipeline status, failed builds, logs, triggering pipelines, or retrying runs.
List recent runs
az pipelines runs list --top 10 --output table
az pipelines runs list --pipeline-ids PIPELINE_ID --result failed --top 5 --output tableView run details and logs
# Show run summary
az pipelines runs show --id RUN_ID --output table
# Get logs via REST (no direct CLI command for logs)
az devops invoke `
--area pipelines `
--resource logs `
--route-parameters project=$config.project pipelineId=PIPELINE_ID runId=RUN_ID `
--api-version 7.0Trigger and retry
# Trigger a pipeline
az pipelines run --name "pipeline-name" --branch main
# Trigger with variables
az pipelines run --name "pipeline-name" --variables "env=staging" "deploy=true"Download artifacts
az pipelines runs artifact download `
--run-id RUN_ID `
--artifact-name "drop" `
--path ./artifactsView pipeline definition
az pipelines show --name "CI Build" --output yamlCommand Cookbook
Use for CLI patterns, bulk ops, REST API calls, and output formatting.
Work Items
Create
# User Story
az boards work-item create `
--type "User Story" `
--title "Implement login page" `
--description "As a user, I want to log in so I can access my account." `
--assigned-to "user@example.com" `
--area $config.areaPaths.story `
--iteration $config.iterationPath
# Bug
az boards work-item create `
--type Bug `
--title "Login button unresponsive on mobile" `
--description "<p>Steps to reproduce:</p><ol><li>Open mobile browser</li><li>Tap login</li></ol>" `
--assigned-to "user@example.com"
# Task under a Story (create then link)
$taskId = az boards work-item create `
--type Task `
--title "Write unit tests for auth module" `
--query "id" -o tsv
az boards work-item relation add --id $taskId --relation-type parent --target-id STORY_IDFor hierarchy creation, see Planning Flow.
Parent-Child Linking
There is no --parent flag on az boards work-item create. Use relation add after creation:
# Link child to parent
az boards work-item relation add --id CHILD_ID --relation-type parent --target-id PARENT_ID
# Link multiple children to same parent
az boards work-item relation add --id PARENT_ID --relation-type child --target-id CHILD1,CHILD2,CHILD3
# View relations on a work item
az boards work-item relation show --id 12345 --output tableRead
# Show single item
az boards work-item show --id 12345 --output table
# Show with all relations/links
az boards work-item show --id 12345 --expand relations
# Note: --fields and --expand cannot be used togetherUpdate
# Change state
az boards work-item update --id 12345 --state Active
az boards work-item update --id 12345 --state Closed
# Reassign
az boards work-item update --id 12345 --assigned-to "other@example.com"
# Update title and description
az boards work-item update --id 12345 `
--title "Updated title" `
--description "Updated description"
# Add tags
az boards work-item update --id 12345 --fields "System.Tags=api,backend,priority"
# Move to different area/iteration
az boards work-item update --id 12345 `
--area $config.areaPaths.story `
--iteration $config.iterationPath
# Update custom fields using --fields
az boards work-item update --id 12345 `
--fields "Microsoft.VSTS.Scheduling.StoryPoints=5"Delete
# Delete (moves to recycle bin)
az boards work-item delete --id 12345 --yes
# Permanently destroy (cannot undo)
az boards work-item delete --id 12345 --destroy --yesBulk operations (PowerShell)
# Bulk close items by query
$ids = az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Resolved'" --output json | ConvertFrom-Json
$ids | ForEach-Object { az boards work-item update --id $_.id --state Closed }
# Bulk assign
@(100, 101, 102) | ForEach-Object {
az boards work-item update --id $_ --assigned-to "dev@example.com"
}Pull Requests
Create
# Basic PR
az repos pr create `
--title "feat: add user authentication" `
--description "Implements OAuth2 login flow. Resolves AB#12345"
# PR with full options
az repos pr create `
--title "feat: add user authentication" `
--description "Implements OAuth2 login flow" `
--source-branch feature/12345-auth `
--target-branch main `
--work-items 12345 12346 `
--reviewers "reviewer@example.com" `
--auto-complete true `
--delete-source-branch true `
--squash true
# Draft PR
az repos pr create `
--title "WIP: refactoring auth module" `
--draftReview and manage
# List active PRs
az repos pr list --status active --output table
# List my PRs
az repos pr list --creator "user@example.com" --output table
# List PRs assigned to me for review
az repos pr list --reviewer "user@example.com" --output table
# Show PR details
az repos pr show --id 42 --output table
# View PR policies/checks status
az repos pr policy list --id 42 --output table
# Approve (vote=10)
az repos pr set-vote --id 42 --vote 10
# Approve with suggestions (vote=5)
az repos pr set-vote --id 42 --vote 5
# Waiting for author (vote=-5)
az repos pr set-vote --id 42 --vote -5
# Reject (vote=-10)
az repos pr set-vote --id 42 --vote -10
# Reset vote (vote=0)
az repos pr set-vote --id 42 --vote 0
# Complete/merge PR
az repos pr update --id 42 --status completed
# Abandon PR
az repos pr update --id 42 --status abandoned
# Add reviewer
az repos pr reviewer add --id 42 --reviewers "reviewer@example.com"
# Add comment (use REST via az devops invoke)
az devops invoke `
--area git `
--resource pullRequestThreads `
--route-parameters project=$config.project repositoryId=REPO pullRequestId=42 `
--api-version 7.0 `
--http-method POST `
--in-file comment.jsonPR work item linking
# Link work items when creating PR
az repos pr create --title "fix: resolve bug" --work-items 12345 12346
# Add work item link to existing PR
az repos pr work-item add --id 42 --work-items 12345
# List linked work items
az repos pr work-item list --id 42 --output tableRepos
Branch management
# List branches
az repos ref list --repository REPO --filter heads/ --output table
# Create branch from main
az repos ref create `
--name "refs/heads/feature/new-branch" `
--repository REPO `
--object-id $(az repos ref list --repository REPO --filter heads/main --query "[0].objectId" -o tsv)
# Delete branch
az repos ref delete `
--name "refs/heads/feature/old-branch" `
--repository REPO `
--object-id COMMIT_SHA
# Lock/unlock branch
az repos ref lock --name "refs/heads/release/1.0" --repository REPO
az repos ref unlock --name "refs/heads/release/1.0" --repository REPOBranch policies
# List policies on a branch
az repos policy list --branch main --repository-id REPO_ID --output table
# Require minimum reviewers
az repos policy approver-count create `
--branch main `
--repository-id REPO_ID `
--minimum-approver-count 2 `
--creator-vote-counts false `
--enabled true `
--blocking true
# Require build validation
az repos policy build create `
--branch main `
--repository-id REPO_ID `
--build-definition-id PIPELINE_ID `
--display-name "CI Must Pass" `
--enabled true `
--blocking true `
--queue-on-source-update-only trueREST API via az devops invoke
# Generic GET
az devops invoke `
--area wit `
--resource workitems `
--route-parameters project=$config.project id=12345 `
--api-version 7.0
# Generic POST with body from file
az devops invoke `
--area wit `
--resource workitems `
--route-parameters project=$config.project `
--api-version 7.0 `
--http-method POST `
--in-file body.json
# Get work item revisions (for cycle time)
az devops invoke `
--area wit `
--resource revisions `
--route-parameters project=$config.project workItemId=12345 `
--api-version 7.0Output formatting
# Table (human readable)
az boards query --wiql "..." --output table
# JSON (for processing)
az boards query --wiql "..." --output json
# TSV (for scripting)
az boards work-item show --id 12345 --query "fields.\"System.Title\"" -o tsv
# JMESPath filtering
az boards query --wiql "..." --output json `
--query "[].fields.{Id: 'System.Id', Title: 'System.Title', State: 'System.State'}"
# Pipe JSON to PowerShell
$items = az boards query --wiql "..." --output json | ConvertFrom-Json
$items | Where-Object { $_.fields.'System.State' -eq 'Active' } | Format-TableBoard Columns API (REST)
Use for board columns, WIP limits, or Kanban column configuration. For board-level metrics, see Backlog Management.
The az boards CLI does not expose board column configuration directly. Use az devops invoke.
Step 1: List boards for a team
Get board IDs for the team:
az devops invoke `
--area work `
--resource boards `
--route-parameters project=$config.project team="{TEAM_NAME}" `
--api-version 7.0 --output jsonReturns board metadata with IDs:
{
"count": 5,
"value": [
{ "id": "...", "name": "Stories" },
{ "id": "...", "name": "Epics" },
{ "id": "...", "name": "Features" }
]
}Step 2: Fetch columns for a specific board
Pass the board id to get column details including WIP limits:
$boardId = "{BOARD_ID}"
$result = az devops invoke `
--area work `
--resource boards `
--route-parameters project=$config.project `
team="{TEAM_NAME}" `
id=$boardId `
--api-version 7.0 --output json
$board = $result | ConvertFrom-Json
foreach ($col in $board.columns) {
$wip = if ($col.itemLimit -and $col.itemLimit -gt 0) { $col.itemLimit } else { "none" }
Write-Host ("{0,-30} WIP Limit: {1}" -f $col.name, $wip)
}Gotchas
- Do NOT use
az restfor this endpoint; responses can include Unicode (infinity symbol U+221E) that triggers Windowscharmaperrors. - Use
az devops invokeinstead, which handles encoding correctly. - The list endpoint (
--resource boardswithoutid) returns metadata only; pass the boardidto get column details. - Set
$env:PYTHONIOENCODING = "utf-8"as a safety net for encoding issues.
Step 3: Query work items by board column
Board columns like "PR", "Testing", "Deployment" may share the same underlying state (e.g., "Active"). Use System.BoardColumn for precise column queries:
az boards query --wiql "SELECT [System.Id], [System.Title], [System.AssignedTo], [System.BoardColumn] `
FROM WorkItems `
WHERE [System.WorkItemType] = 'User Story' `
AND [System.AreaPath] UNDER $config.areaPaths.story `
AND [System.BoardColumn] = 'PR' `
AND [System.State] <> 'Removed' `
ORDER BY [System.ChangedDate] DESC" --output tableAvailable board column values
These match the column names from Step 2. For the Stories board:
| Board Column | Underlying State | WIP Limit |
|---|---|---|
| New | New | none |
| Ready | Ready to Code | none |
| Active | Active | 8 |
| PR | Active | 5 |
| Testing | Active | 3 |
| Deployment | Active | 3 |
| Closed | Closed | none |
Key insight
Multiple board columns can map to the same ADO state (e.g., "PR", "Testing", "Deployment" all map to "Active"). Querying by System.State = 'Active' returns items across all those columns. Use System.BoardColumn for column-level precision.
Check WIP compliance
Compare column item count against WIP limit:
$columns = @("Active", "PR", "Testing", "Deployment")
foreach ($col in $columns) {
$items = az boards query --wiql "SELECT [System.Id] FROM WorkItems `
WHERE [System.WorkItemType] = 'User Story' `
AND [System.AreaPath] UNDER $config.areaPaths.story `
AND [System.BoardColumn] = '$col' `
AND [System.State] <> 'Removed'" --output json | ConvertFrom-Json
Write-Host "$col : $($items.Count) items"
}API reference
- Boards - Get -
GET {org}/{project}/{team}/_apis/work/boards/{board}?api-version=7.0 - Boards - List -
GET {org}/{project}/{team}/_apis/work/boards?api-version=7.0 - WIQL BoardColumn -
System.BoardColumnfield reference
WIQL Reference
Use for field names, operators, macros, quoting rules, or link query syntax. WIQL is used with az boards query --wiql "...".
Clause structure
SELECT [Field1], [Field2]
FROM WorkItems -- or WorkItemLinks for link queries
WHERE [Condition1] AND [Condition2]
ORDER BY [Field] ASC|DESC
ASOF 'YYYY-MM-DD' -- optional: historical point-in-time querySELECT *is not supported; list each field explicitly.- Use
FROM WorkItemsfor flat queries,FROM WorkItemLinksfor link/hierarchy queries. ASOFmust be the last clause and is not compatible with link queries.
Field names
Always use bracket syntax: [System.FieldName].
Common fields
| Field | Description |
|---|---|
[System.Id] |
Work item ID |
[System.Title] |
Title (single-line text) |
[System.State] |
Current state (New, Active, Resolved, Closed, Removed) |
[System.WorkItemType] |
Type (Epic, Feature, User Story, Task, Bug) |
[System.AssignedTo] |
Assigned person (identity field) |
[System.CreatedDate] |
Creation date |
[System.ChangedDate] |
Last modified date |
[System.CreatedBy] |
Creator (identity field) |
[System.Tags] |
Tags (comma-separated string) |
[System.AreaPath] |
Area path (tree path) |
[System.IterationPath] |
Iteration path (tree path) |
[System.Description] |
Description (HTML/rich-text) |
[System.TeamProject] |
Project name |
[System.BoardColumn] |
Current Kanban column |
[System.BoardColumnDone] |
Whether in done split of column |
Priority and effort
| Field | Description |
|---|---|
[Microsoft.VSTS.Common.Priority] |
Priority (1=Critical, 2=High, 3=Medium, 4=Low) |
[Microsoft.VSTS.Common.Severity] |
Bug severity |
[Microsoft.VSTS.Scheduling.StoryPoints] |
Story points |
[Microsoft.VSTS.Scheduling.Effort] |
Effort |
[Microsoft.VSTS.Common.ValueArea] |
Business or Architectural |
Dates and history
| Field | Description |
|---|---|
[Microsoft.VSTS.Common.ClosedDate] |
Date item was closed |
[Microsoft.VSTS.Common.ResolvedDate] |
Date item was resolved |
[Microsoft.VSTS.Common.ActivatedDate] |
Date item was activated |
[Microsoft.VSTS.Common.StateChangeDate] |
Last state change date |
Operators
Comparison
| Operator | Field Types | Example |
|---|---|---|
= |
All | [System.State] = 'Active' |
<> |
All | [System.State] <> 'Removed' |
>, <, >=, <= |
Number, Date | [Microsoft.VSTS.Common.Priority] <= 2 |
IN |
String, Number | [System.State] IN ('New', 'Active') |
NOT IN |
String, Number | [System.State] NOT IN ('Closed', 'Removed') |
Text search
| Operator | Field Types | Indexed | Example |
|---|---|---|---|
CONTAINS |
String (single-line) | No | [System.Title] CONTAINS 'auth' |
NOT CONTAINS |
String (single-line) | No | [System.Title] NOT CONTAINS 'test' |
CONTAINS WORDS |
String, PlainText, HTML | Yes | [System.Description] CONTAINS WORDS 'authentication failed' |
NOT CONTAINS WORDS |
String, PlainText, HTML | Yes | [System.Description] NOT CONTAINS WORDS 'obsolete' |
Notes:
CONTAINSis a slow unindexed substring scan.CONTAINS WORDSis indexed, faster, and supports stemming.- Use
CONTAINS WORDSfor Description/repro steps; useCONTAINSfor Title/Tags. CONTAINS WORDSlimit: 100 characters max in search phrase.
Path operators
| Operator | Field Types | Example |
|---|---|---|
UNDER |
AreaPath, IterationPath | [System.AreaPath] UNDER '{AREA_PATH}' |
NOT UNDER |
AreaPath, IterationPath | [System.IterationPath] NOT UNDER '{ITERATION_PATH}' |
UNDER matches the path itself and all children beneath it.
Identity operators
| Operator | Field Types | Example |
|---|---|---|
IN GROUP |
Identity fields | [System.AssignedTo] IN GROUP 'My Team' |
NOT IN GROUP |
Identity fields | [System.AssignedTo] NOT IN GROUP 'Contractors' |
IN GROUP checks membership in an ADO security group or team.
Historical operators
| Operator | Field Types | Example |
|---|---|---|
WAS EVER |
Identity, State, picklist | [System.AssignedTo] WAS EVER 'alice@example.com' |
EVER |
State | [System.State] EVER 'Active' |
WAS EVER checks if a field ever had a value at any point in history.
Null checks
| Operator | Example |
|---|---|
= '' |
[System.AssignedTo] = '' (unassigned) |
<> '' |
[System.AssignedTo] <> '' (assigned) |
Macros
| Macro | Description |
|---|---|
@Me |
Current authenticated user |
@Today |
Today's date (midnight, server timezone) |
@Today - N |
N days ago |
@Today + N |
N days from now |
@Project |
Current project name |
@CurrentIteration |
Current team iteration (requires team context) |
@CurrentIteration + N |
N iterations ahead |
@CurrentIteration - N |
N iterations back |
Warning: @CurrentIteration requires team context and may not resolve in all CLI contexts. Use explicit iteration paths when querying via az boards query.
Query patterns
Basic SELECT
SELECT [System.Id], [System.Title], [System.State]
FROM WorkItems
WHERE [System.WorkItemType] = 'User Story'
AND [System.State] = 'Active'
ORDER BY [Microsoft.VSTS.Common.Priority]Historical query (ASOF)
SELECT [System.Id], [System.Title], [System.State], [System.AssignedTo]
FROM WorkItems
WHERE [System.WorkItemType] = 'User Story'
AND [System.State] = 'Active'
ORDER BY [System.ChangedDate] DESC
ASOF '2025-01-15'- Date format:
'YYYY-MM-DD'(ISO 8601 recommended). - Cannot be used with
FROM WorkItemLinks(flat queries only). - Returns work item data as it existed on that date.
Hierarchical (Parent-Child)
WARNING: az boards query silently returns empty results for FROM WorkItemLinks. Use REST API via az devops invoke instead:
# Save WIQL to a temp file (required for POST body)
$body = '{"query": "SELECT [System.Id], [System.Title] FROM WorkItemLinks WHERE (Source.[System.Id] = 12345) AND ([System.Links.LinkType] = ''System.LinkTypes.Hierarchy-Forward'') MODE (MustContain)"}'
$body | Out-File -Encoding utf8 wiql-query.json
az devops invoke `
--area wit `
--resource wiql `
--http-method POST `
--api-version 7.0 `
--route-parameters project=$config.project `
--in-file wiql-query.json `
--output json
Remove-Item wiql-query.jsonThe response contains workItemRelations with source/target ID pairs; fetch full work item details separately.
For flat queries that find children without link syntax, use:
SELECT [System.Id], [System.Title], [System.WorkItemType]
FROM WorkItems
WHERE [System.WorkItemType] = 'Feature'
AND [System.AreaPath] UNDER '{AREA_PATH}'Or use az boards work-item relation show --id PARENT_ID to list children directly.
Link query WIQL syntax (REST API)
Find all stories under all features:
SELECT [System.Id], [System.Title], [System.WorkItemType]
FROM WorkItemLinks
WHERE (Source.[System.WorkItemType] = 'Feature')
AND ([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward')
AND (Target.[System.WorkItemType] = 'User Story')
MODE (Recursive)Find all descendants of a specific work item:
SELECT [System.Id], [System.Title], [System.WorkItemType]
FROM WorkItemLinks
WHERE (Source.[System.Id] = 12345)
AND ([System.Links.LinkType] = 'System.LinkTypes.Hierarchy-Forward')
MODE (Recursive, MustContain)Related links
SELECT [System.Id], [System.Title]
FROM WorkItemLinks
WHERE (Source.[System.Id] = 12345)
AND ([System.Links.LinkType] = 'System.LinkTypes.Related-Forward')
MODE (MustContain)Link query modes
| Mode | Behavior |
|---|---|
MustContain |
Only return items that have matching links |
MayContain |
Return items regardless of whether they have matching links |
DoesNotContain |
Only return items that do NOT have matching links |
Recursive |
Traverse hierarchy recursively (all descendants) |
Modes can be combined: MODE (Recursive, MustContain)
Common link types
| Link Type | Description |
|---|---|
System.LinkTypes.Hierarchy-Forward |
Parent -> Child |
System.LinkTypes.Hierarchy-Reverse |
Child -> Parent |
System.LinkTypes.Related-Forward |
Related item |
System.LinkTypes.Dependency-Forward |
Successor (depends on) |
System.LinkTypes.Dependency-Reverse |
Predecessor |
List all link types in your org: az boards work-item relation list-type --output table
az boards query usage
# Flat query
az boards query --wiql "SELECT [System.Id], [System.Title] FROM WorkItems WHERE [System.State] = 'Active'" --output table
# Output as JSON for processing
az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Active'" --output json
# Query by saved query ID (from ADO web portal)
az boards query --id QUERY_ID --output table
# Query with org/project override
az boards query --wiql "..." --org https://dev.azure.com/ORG --project PROJECTPowerShell quoting for WIQL
Rules:
- Outer quotes: use double quotes
"to wrap the--wiqlvalue. - Inner string values: use single quotes inside WIQL.
- Escape single quotes in values by doubling them.
Examples:
# Standard query - double outside, single inside
az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.State] = 'Active'" --output table
# Value containing a single quote (O'Brien)
az boards query --wiql "SELECT [System.Id] FROM WorkItems WHERE [System.Title] CONTAINS 'O''Brien'"
# Multi-line query in PowerShell (use backtick for continuation)
az boards query --wiql "SELECT [System.Id], [System.Title], [System.State] `
FROM WorkItems `
WHERE [System.WorkItemType] = 'User Story' `
AND [System.State] = 'Active' `
AND [System.AreaPath] UNDER '{AREA_PATH}' `
ORDER BY [Microsoft.VSTS.Common.Priority]" --output table
# Store WIQL in a variable for complex queries
$wiql = @"
SELECT [System.Id], [System.Title], [System.State]
FROM WorkItems
WHERE [System.WorkItemType] = 'User Story'
AND [System.State] IN ('New', 'Active')
AND [System.AreaPath] UNDER '{AREA_PATH}'
ORDER BY [System.ChangedDate] DESC
"@
az boards query --wiql $wiql --output tableTip: For complex queries, the here-string (@"..."@) approach avoids quoting issues.
Gotchas
Quoting and syntax
- String values use single quotes inside WIQL:
'Active'not"Active". - To embed a literal single quote, double it:
'O''Brien'. - Field names are case-insensitive but bracket syntax
[...]is required. SELECT *is not supported - always list fields explicitly.
Operators
CONTAINSis a slow unindexed substring scan - avoid on large result sets.CONTAINS WORDSis fast indexed full-text - prefer for Description/repro steps.CONTAINS WORDSsupports stemming ("run" matches "running").UNDERmatches the path itself and all children beneath it.WAS EVERworks on identity and picklist fields, not just[System.State].
Dates
- Date comparisons use server timezone, not local timezone.
- No date arithmetic between fields; compute date diffs outside WIQL.
@Todayresolves to midnight server time.
Limits and restrictions
- Maximum 20,000 results per query.
ORDER BYsupports one field, ascending (default) orDESC.ASOFis not compatible withFROM WorkItemLinks(flat queries only).az boards querysilently returns empty forFROM WorkItemLinks- use REST API viaaz devops invoke.@CurrentIterationrequires team context and may fail in CLI without team scope.- REST API WIQL endpoint returns only IDs; a second call is needed to fetch field values.
- Area/iteration paths should not contain quotes (no escape mechanism).
--fieldsand--expandcannot be used together onaz boards work-item show.