GitHub Projects v2 views and custom fields, built-in workflow automation, milestones vs Projects, YAML issue templates, label taxonomy at scale, DORA metrics via GraphQL, the Insights tab, and where third-party analytics tools add value beyond what GitHub provides natively
GitHub Projects v2 (introduced 2022) is a first-class project management layer on top of issues and PRs. Unlike the original Projects (which were simple kanban boards), v2 supports multiple views, custom fields, group-by, sort, filter, and GraphQL-based automation — all without leaving GitHub.
📋 Table view
Spreadsheet-like grid. Every item is a row; custom fields are columns. Best for engineering leads doing sprint planning, bulk editing fields across multiple issues, or comparing items by priority and size. Supports multi-sort, filter expressions, and in-line editing.
📌 Board view (Kanban)
Cards grouped into columns by a field (typically Status). Best for developers tracking daily work-in-progress, standups, and visible flow. Drag-and-drop between columns updates the field value. Supports swimlane grouping by a second field (e.g. assignee).
🗺️ Roadmap view
Timeline/Gantt chart. Items are bars on a horizontal timeline. Best for communicating quarterly goals to stakeholders, showing feature scheduling, and spotting schedule conflicts. Requires date fields (start date + target date) or iteration fields on each item.
Creating a project and adding repos
# Create a project for your org$ gh project create --owner acme --title "Q3 2026 Platform Sprint" --format json
# Add all open issues from a repo to the project$ gh issue list --repo acme/platform --state open --json number \
--jq '.[].number' \
| xargs -I{} gh project item-add 42 --owner acme --url \
"https://github.com/acme/platform/issues/{}"
# List all items in a project$ gh project item-list 42 --owner acme --format json | jq '.items[].content.title'
Choosing the right view per audience
Audience
Best view
Key fields to show
Daily standup (team)
Board (Status columns)
Status, Assignee, Priority
Sprint planning (lead)
Table
Priority, Size, Iteration, Assignee, Status
Quarterly roadmap (stakeholders)
Roadmap
Target Date, Milestone, Status, Team
Backlog grooming
Table (filtered: Status=Backlog)
Priority, Size, Labels, Milestone
Bug triage
Table (filtered: Label=bug)
Priority, Severity, Assignee, Area
11.2
Custom Fields: Status, Priority, Story Points, Iteration & Date
📚 Free Weekly Tutorials
Java, Spring Boot, AWS, DevOps & AI — straight to your inbox.
Custom fields are the difference between a Projects board that mirrors what's already in issue labels and one that genuinely supports your workflow. Add fields that your team needs to make decisions — not every field from Jira.
Field types
Single selectDropdown with predefined options. Best for Status (Todo / In Progress / In Review / Done), Priority (P0 / P1 / P2 / P3), Team. Options can have colours.
TextFree-text string. Best for Notes, Risk description, Blocked by reference, spec link.
NumberNumeric value. Best for Story Points, Complexity, T-shirt size (mapped 1/2/3/5/8). Used in sum aggregations in Table view.
DateA specific date. Best for Target Date, Due Date. Required for the Roadmap view timeline. Displayed as calendar picker in the UI.
IterationA recurring time-box (sprint). GitHub auto-manages iteration start/end dates. Best for Sprint / Iteration. Supports "current iteration" in filter expressions. Built-in "previous/current/next" iteration filtering.
Recommended minimal field set
Fields to add to every project (beyond built-ins like Title, Assignees, Labels):
Status single-select Todo | In Progress | In Review | Blocked | Done
Priority single-select P0-Critical | P1-High | P2-Medium | P3-Low
Size single-select XS | S | M | L | XL
Sprint iteration 2-week cadence
Target Date date for roadmap view
Team single-select Platform | Payments | Auth | Frontend
Area text links to CODEOWNERS area (e.g. services/payments)
Field expressions in Table view
# Filter syntax in the project's filter bar:
sprint:@current status:In-Progress ← current sprint + in progress
assignee:@me priority:P0,P1 ← my high-priority items
-label:blocked is:open ← open, not blocked
iteration:@previous status:Done ← completed last sprint (for review)
no:assignee status:Todo ← unassigned backlog items
11.3
Workflow Automation in Projects
GitHub Projects v2 has built-in workflow automation (no Actions required) for common status transitions. Configure under Project settings → Workflows.
Built-in automation triggers
Trigger
Default action
Customise to
Item added to project
Set Status = Todo
Set any single-select field; add to a specific iteration
Item closed (issue closed / PR merged)
Set Status = Done
Also archive the item after N days
Item reopened
Set Status = Todo
Set Priority = P1 (reopened items are often urgent)
PR opened for issue
Set Status = In Progress
Auto-assign to PR author
PR review requested
Set Status = In Review
—
PR review approved
—
Set Status = Ready to Merge
Advanced automation via Actions + GraphQL
For logic the built-in workflows can't express, use a GitHub Actions workflow with the GraphQL API to update project fields:
# .github/workflows/project-automation.ymlon:issues:types: [labeled]
jobs:set-priority:runs-on: ubuntu-latest
if:github.event.label.name == 'P0-critical'steps:
- name:Set priority field in projectenv:GH_TOKEN:${{ secrets.PROJECT_TOKEN }}ISSUE_ID:${{ github.event.issue.node_id }}run: |
# 1. Find the project item ID for this issue
ITEM_ID=$(gh api graphql -f query='
query($id: ID!) {
node(id: $id) {
... on Issue {
projectItems(first: 1) {
nodes { id }
}
}
}
}' -F id="$ISSUE_ID" --jq '.data.node.projectItems.nodes[0].id')
# 2. Update the Priority field
gh api graphql -f query='
mutation($project: ID!, $item: ID!, $field: ID!, $value: String!) {
updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $field
value: { singleSelectOptionId: $value }
}) { projectV2Item { id } }
}' \
-F project="$PROJECT_NODE_ID" \
-F item="$ITEM_ID" \
-F field="$PRIORITY_FIELD_ID" \
-F value="$P0_OPTION_ID"
NODE IDs FOR GRAPHQL
The GraphQL API uses opaque node IDs, not numeric IDs. Get them with gh api graphql -f query='{ viewer { projectsV2(first:10) { nodes { id title } } } }' for projects, and similar queries for field IDs and option IDs. Run these once and store the IDs in Actions variables — they're stable.
11.4
Milestones vs Projects: When Each Wins
Milestones and Projects are complementary, not alternatives. Understanding what each does best prevents duplication of effort.
Dimension
Milestones
Projects v2
Scope
Single repo
Cross-repo (issues and PRs from any repo)
Progress tracking
Built-in: X of Y issues closed (shown as a progress bar)
Custom: filter by Status=Done, group by Sprint
Due date
One due date per milestone
Per-item date fields
Filtering
Filter issues by milestone in list view
Rich filter expressions across all custom fields
Best for
Tracking a specific release: "All issues that must be done for v2.5.0"
Sprint planning, roadmap, cross-repo initiatives
Closing keyword integration
Merging a PR with "Closes #42" closes issue #42 and updates milestone progress
Built-in workflow: closed issue → Status=Done
Using both together
A practical setup for a team shipping versioned releases:
Milestone per version — v2.5.0, v2.6.0. All issues that must be fixed for that release are assigned to the milestone. The milestone progress bar gives a simple "% done for this release" view.
Project for sprint planning — issues from multiple repos, custom fields for priority and size, iteration tracking. Pull from the milestone's issues into the current sprint. A release ships when the milestone hits 100%.
# Create a milestone via CLI$ gh api repos/acme/platform/milestones \
--method POST \
--field title="v2.5.0" \
--field due_on="2026-07-31T00:00:00Z" \
--field description="July release — new checkout flow + auth improvements"
# List open milestones with progress$ gh api repos/acme/platform/milestones \
--jq '.[] | {title, due: .due_on, open: .open_issues, closed: .closed_issues}'
11.5
Issue Templates: YAML Front Matter & Structured Forms
YAML issue templates (available since 2021) render as structured forms rather than freetext Markdown — they're harder to skip past and produce consistent, machine-readable data.
YAML template structure
# .github/ISSUE_TEMPLATE/bug_report.ymlname:Bug Reportdescription:File a bug report for a confirmed defecttitle:"[Bug]: "labels: [bug, needs-triage]
assignees: []
body:
- type:markdownattributes:value:Thanks for taking the time to fill this out. Please provide as much detail as possible.
- type:inputid:summaryattributes:label:Summarydescription:A clear one-line summary of the bugplaceholder:e.g. "Payment form throws 500 when amount is null"validations:required:true
- type:dropdownid:severityattributes:label:Severityoptions:
- Critical — service down / data loss
- High — core feature broken
- Medium — feature degraded
- Low — cosmetic / minorvalidations:required:true
- type:dropdownid:areaattributes:label:Affected areamultiple:falseoptions: [Payments, Auth, Notifications, API, Frontend, Infrastructure]
- type:textareaid:reproattributes:label:Steps to reproducedescription:Numbered steps. Include environment, version, and any relevant config.render:markdownvalidations:required:true
- type:textareaid:expectedattributes:label:Expected behaviourvalidations:required:true
- type:textareaid:actualattributes:label:Actual behaviourdescription:Include any error messages, stack traces, or screenshots
- type:checkboxesid:checklistattributes:label:Pre-submission checklistoptions:
- label:I have searched existing issues and this is not a duplicaterequired:true
- label:I have reproduced this on the latest version
Feature request template
# .github/ISSUE_TEMPLATE/feature_request.ymlname:Feature Requestdescription:Propose a new feature or improvementtitle:"[Feature]: "labels: [feature, needs-triage]
body:
- type:textareaid:problemattributes:label:What problem does this solve?description:Describe the job-to-be-done. "As a [role], I need to [do X] so that [outcome Y]."validations:required:true
- type:textareaid:solutionattributes:label:Proposed solutiondescription:How you envision this working. Wireframes, API sketches, or pseudocode welcome.
- type:dropdownid:impactattributes:label:Business impactoptions: [High — affects many users, Medium — affects some users, Low — nice to have]
validations:required:true
11.6
Labels at Scale: Taxonomy & Cross-Repo Sync
Labels are only useful if they're consistent. Inconsistency across repos — different names for the same concept, missing labels, conflicting colours — makes cross-repo filtering and automation unreliable.
Recommended label taxonomy
# TYPE labels (what kind of work is this?)
bug #d73a4a A confirmed defect
feature #0075ca New functionality
enhancement #a2eeef Improvement to existing feature
refactor #e4e669 Code restructuring, no behaviour change
docs #0075ca Documentation only
chore #ffffff Build, CI, dependency update
security #d73a4a Security fix or vulnerability
# PRIORITY labels (how urgent?)
priority/critical #b60205 Service down / data loss
priority/high #d93f0b Impacts core workflows
priority/medium #fbca04 Should be in next sprint
priority/low #0e8a16 Nice to have
# STATUS labels (what state is it in?)
needs-triage #ededed Not yet reviewed
needs-info #d4c5f9 Waiting on reporter
blocked #fef2c0 Blocked by external dependency
wontfix #ffffff Intentionally not addressing
duplicate #cfd3d7 Duplicate of another issue
# AREA labels (which part of the system?)
area/payments #bfd4f2 Payments service
area/auth #bfd4f2 Authentication
area/frontend #bfd4f2 UI / frontend
area/infra #bfd4f2 Infrastructure / platform
# SIZE labels (effort estimate)
size/xs #c5def5 < 1 day
size/s #c5def5 1–2 days
size/m #c5def5 3–5 days
size/l #c5def5 1–2 weeks
size/xl #c5def5 Needs breaking down
Syncing labels across repos with EndBug/label-sync
# .github/labels.yml — source of truth in the .github org repo
- name:bugcolor:d73a4adescription:A confirmed defect
- name:featurecolor:0075cadescription:New functionality# ... all labels
# .github/workflows/sync-labels.yml — runs in the .github org repoon:push:paths: ['.github/labels.yml']
schedule:
- cron:'0 4 * * 1'# weekly drift correctionjobs:sync:runs-on: ubuntu-latest
steps:
- uses:actions/checkout@v4
- uses:EndBug/label-sync@v2with:config-file:.github/labels.ymldelete-other-labels:true# remove labels not in the configsource-repo:acme/.github# sync FROM this repo's labels.ymltoken:${{ secrets.ORG_PAT }}# PAT with repo scope across the org
11.7
Engineering Metrics: DORA via GraphQL & Cycle Time
The DORA (DevOps Research and Assessment) four metrics are the most research-backed measures of engineering team performance. GitHub's GraphQL API provides the raw data to compute proxies for all four.
1. Deployment Frequency
How often does your team deploy to production?
Elite: Multiple deploys/dayLow: Monthly or less
GitHub proxy: Count of merged PRs to main per day, or count of GitHub Deployments created with environment=production per week.
2. Lead Time for Changes
How long from first commit to running in production?
Elite: <1 hourLow: 1–6 months
GitHub proxy: Time from first commit in a PR to PR merge (cycle time). Add deployment lag if you track GitHub Deployments.
3. Change Failure Rate
What percentage of deployments cause a production incident?
Elite: 0–15%Low: >45%
GitHub proxy: Ratio of PRs labelled hotfix or incident to total merged PRs. Imperfect — incidents logged in PagerDuty/Linear are more accurate.
4. Mean Time to Restore (MTTR)
How long to recover from a production incident?
Elite: <1 hourLow: >1 week
GitHub proxy: Time from an issue labelled incident being opened to it being closed. Better tracked in your incident management tool.
Extracting cycle time via GraphQL
# Cycle time = time from first commit to merge, for the last 30 PRs
gh api graphql -f query='
query($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
pullRequests(
states: MERGED
orderBy: {field: UPDATED_AT, direction: DESC}
first: 30
) {
nodes {
number
mergedAt
commits(first: 1) {
nodes {
commit { authoredDate }
}
}
}
}
}
}' -F owner=acme -F repo=platform \
--jq '.data.repository.pullRequests.nodes | map({
pr: .number,
mergedAt: .mergedAt,
firstCommit: .commits.nodes[0].commit.authoredDate,
cycleHours: (
(.mergedAt | fromdateiso8601) -
(.commits.nodes[0].commit.authoredDate | fromdateiso8601)
) / 3600 | round
})'
Deployment frequency from GitHub Deployments
# Count production deployments per day over the last 30 days$ gh api "repos/acme/platform/deployments?environment=production&per_page=100" \
--jq 'group_by(.created_at[:10]) | map({date: .[0].created_at[:10], count: length})'
Commit frequency calendar (contribution graph) — by day of week and time of day
Understanding team work patterns; seeing productivity trends
Code frequency
Lines added vs deleted per week
Spotting refactoring sprints; large deletion spikes may indicate dead code removal
Dependency graph
All dependencies and dependents — what depends on this repo's packages
Impact assessment before removing a package; visibility into what you're depending on
Network
Branching history visualisation across forks
Understanding fork relationships; rarely used day-to-day
Forks
List of all forks with last commit date
Finding active forks; open-source contribution scouting
Traffic (private repos: owner/admin only)
Views, unique visitors, clones, referring sites, popular content pages — over the last 14 days
Understanding which docs pages get traffic; where clones come from
TRAFFIC DATA RETENTION
Traffic data (views, clones) is only retained for 14 days in the Insights tab. If you need historical traffic data, set up a scheduled Actions workflow to export it via the REST API and store it in a time-series database or a simple CSV in a private repo.
Third-Party Analytics: What They Add Over Native GitHub
GitHub's native Insights are useful but limited — no historical retention beyond 14 days for traffic, no team-level aggregation, no DORA dashboards, no benchmarking against industry data. Third-party tools fill these gaps.
LinearBEngineering metrics platform focused on DORA and developer experience. Connects to GitHub + Jira/Linear. Provides team-level cycle time, PR review time, deployment frequency, and "developer time" (time spent coding vs reviewing vs in meetings). Has GitStream for automated workflow rules (auto-assign reviewers based on changed files, auto-label PRs). Good for teams that want out-of-the-box DORA dashboards without writing GraphQL.
JellyfishEngineering management analytics — connects GitHub, Jira, calendar, and video call data. Shows "investment allocation" (what % of engineering time goes to features vs debt vs incidents). More valuable for VPs of Engineering than individual teams. Requires access to a lot of data sources to be useful.
SwarmiaDeveloper-first metrics. Focuses on flow efficiency: cycle time breakdown (coding / waiting for review / reviewing / waiting to merge), PR size distribution, meeting load vs focus time. Shows metrics per team and per individual (with privacy options). Strong on actionable recommendations — surfaces "largest contributors to slow cycle time" with root causes.
PullPanda / AxoloPR collaboration tools. Axolo creates a dedicated Slack channel for each PR, routes review notifications there, and auto-archives when merged. Reduces notification noise and keeps PR discussion in one place. Not a metrics tool — focuses on reviewer experience.
GitHub's own Metrics (beta)GitHub is building native engineering metrics (visible under Insights → Metrics in some Enterprise plans). Currently shows DORA metrics, PR cycle time, and deployment frequency with historical retention. Watch this space — it may reduce the need for third-party tools for baseline metrics.
BEFORE BUYING A TOOL
Write a GraphQL query first. The GitHub API can answer most DORA questions for free. Tools add value when you need: (1) historical data beyond what the API retains, (2) team-level aggregation across many repos, (3) correlation with calendar/meeting data, or (4) benchmarks against industry data. If you just need cycle time and PR review SLA reports, a 50-line Python script hitting the GraphQL API is often enough.
Up Next — Phase 12: GitHub CLI & API Automation
The gh CLI for scripting, REST vs GraphQL API decision guide, Octokit SDK, building GitHub Apps with webhook handling, and practical automation scripts for common org workflows.