PHASE 11 OF 14

GitHub Projects & Engineering Metrics

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 DORA Metrics Issue Templates Labels Engineering Metrics Roadmap
11.1

GitHub Projects v2: Board, Table & Roadmap Views

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

AudienceBest viewKey fields to show
Daily standup (team)Board (Status columns)Status, Assignee, Priority
Sprint planning (lead)TablePriority, Size, Iteration, Assignee, Status
Quarterly roadmap (stakeholders)RoadmapTarget Date, Milestone, Status, Team
Backlog groomingTable (filtered: Status=Backlog)Priority, Size, Labels, Milestone
Bug triageTable (filtered: Label=bug)Priority, Severity, Assignee, Area
11.2

Custom Fields: Status, Priority, Story Points, Iteration & Date

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 select Dropdown with predefined options. Best for Status (Todo / In Progress / In Review / Done), Priority (P0 / P1 / P2 / P3), Team. Options can have colours.
Text Free-text string. Best for Notes, Risk description, Blocked by reference, spec link.
Number Numeric value. Best for Story Points, Complexity, T-shirt size (mapped 1/2/3/5/8). Used in sum aggregations in Table view.
Date A specific date. Best for Target Date, Due Date. Required for the Roadmap view timeline. Displayed as calendar picker in the UI.
Iteration A 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

TriggerDefault actionCustomise to
Item added to projectSet Status = TodoSet any single-select field; add to a specific iteration
Item closed (issue closed / PR merged)Set Status = DoneAlso archive the item after N days
Item reopenedSet Status = TodoSet Priority = P1 (reopened items are often urgent)
PR opened for issueSet Status = In ProgressAuto-assign to PR author
PR review requestedSet Status = In Review
PR review approvedSet 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.yml on: issues: types: [labeled] jobs: set-priority: runs-on: ubuntu-latest if: github.event.label.name == 'P0-critical' steps: - name: Set priority field in project env: 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.

DimensionMilestonesProjects v2
ScopeSingle repoCross-repo (issues and PRs from any repo)
Progress trackingBuilt-in: X of Y issues closed (shown as a progress bar)Custom: filter by Status=Done, group by Sprint
Due dateOne due date per milestonePer-item date fields
FilteringFilter issues by milestone in list viewRich filter expressions across all custom fields
Best forTracking a specific release: "All issues that must be done for v2.5.0"Sprint planning, roadmap, cross-repo initiatives
Closing keyword integrationMerging a PR with "Closes #42" closes issue #42 and updates milestone progressBuilt-in workflow: closed issue → Status=Done

Using both together

A practical setup for a team shipping versioned releases:

  • Milestone per versionv2.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.yml name: Bug Report description: File a bug report for a confirmed defect title: "[Bug]: " labels: [bug, needs-triage] assignees: [] body: - type: markdown attributes: value: Thanks for taking the time to fill this out. Please provide as much detail as possible. - type: input id: summary attributes: label: Summary description: A clear one-line summary of the bug placeholder: e.g. "Payment form throws 500 when amount is null" validations: required: true - type: dropdown id: severity attributes: label: Severity options: - Critical — service down / data loss - High — core feature broken - Medium — feature degraded - Low — cosmetic / minor validations: required: true - type: dropdown id: area attributes: label: Affected area multiple: false options: [Payments, Auth, Notifications, API, Frontend, Infrastructure] - type: textarea id: repro attributes: label: Steps to reproduce description: Numbered steps. Include environment, version, and any relevant config. render: markdown validations: required: true - type: textarea id: expected attributes: label: Expected behaviour validations: required: true - type: textarea id: actual attributes: label: Actual behaviour description: Include any error messages, stack traces, or screenshots - type: checkboxes id: checklist attributes: label: Pre-submission checklist options: - label: I have searched existing issues and this is not a duplicate required: true - label: I have reproduced this on the latest version

Feature request template

# .github/ISSUE_TEMPLATE/feature_request.yml name: Feature Request description: Propose a new feature or improvement title: "[Feature]: " labels: [feature, needs-triage] body: - type: textarea id: problem attributes: 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: textarea id: solution attributes: label: Proposed solution description: How you envision this working. Wireframes, API sketches, or pseudocode welcome. - type: dropdown id: impact attributes: label: Business impact options: [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: bug color: d73a4a description: A confirmed defect - name: feature color: 0075ca description: New functionality # ... all labels
# .github/workflows/sync-labels.yml — runs in the .github org repo on: push: paths: ['.github/labels.yml'] schedule: - cron: '0 4 * * 1' # weekly drift correction jobs: sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: EndBug/label-sync@v2 with: config-file: .github/labels.yml delete-other-labels: true # remove labels not in the config source-repo: acme/.github # sync FROM this repo's labels.yml token: ${{ 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/day    Low: 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 hour    Low: 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 hour    Low: >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})'
11.8

Insights Tab: Contribution Graphs, Traffic & Referrers

The repository Insights tab exposes metrics that don't require any API calls — useful for quick checks without writing GraphQL.

What's in each Insights section

SectionWhat it showsUseful for
PulsePRs opened/merged, issues opened/closed, commits, active contributors — for a configurable period (day/week/month)Quick weekly team health check; detecting when PR throughput drops
ContributorsCommit counts, additions, deletions per contributor over timeSpotting bus-factor risk; checking that contributions are distributed
Community StandardsChecklist: README, CONTRIBUTING, SECURITY.md, issue templates, PR template, Code of Conduct, CODEOWNERSQuickly seeing which health files are missing
CommitsCommit frequency calendar (contribution graph) — by day of week and time of dayUnderstanding team work patterns; seeing productivity trends
Code frequencyLines added vs deleted per weekSpotting refactoring sprints; large deletion spikes may indicate dead code removal
Dependency graphAll dependencies and dependents — what depends on this repo's packagesImpact assessment before removing a package; visibility into what you're depending on
NetworkBranching history visualisation across forksUnderstanding fork relationships; rarely used day-to-day
ForksList of all forks with last commit dateFinding 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 daysUnderstanding 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.

Exporting traffic data before it expires

# .github/workflows/export-traffic.yml on: schedule: - cron: '0 6 * * *' # daily export jobs: export: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - run: | DATE=$(date +%Y-%m-%d) gh api repos/acme/platform/traffic/views \ --jq ".views[] | [\"$DATE\", .timestamp, .count, .uniques] | @csv" \ >> traffic/views.csv git add traffic/views.csv git commit -m "chore: export traffic data $DATE" || true git push env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
11.9

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.

LinearB Engineering 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.
Jellyfish Engineering 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.
Swarmia Developer-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 / Axolo PR 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.

Continue to Phase 12 → Back to Hub