skip to content

How would you keep a GitHub Projects board updated as PRs move, without dragging cards?

level: seniorimportance: should knowfreq 40%

answer

  1. Prefer the rules that ship with the project
  2. Auto-add removes 'someone forgot'
  3. Merging closes the issue, closing moves the card
  4. Escalate to GraphQL only for the rest
  5. Org-owned project sits outside the repo token

basics

~20 s

Lean on the project's built-in workflows first — auto-add items by filter, then set Status when an item is added, a review is requested or approved, a pull request merges, or an item closes. Reach for an Action or the GraphQL API only for what those cannot express.

solid answer

~50 s

GitHub Projects ship **built-in workflows** you enable per project: *Auto-add to project* (a repository plus a filter, so matching new issues and pull requests appear without anyone remembering), *Item added to project*, *Item reopened*, *Item closed*, *Code review approved*, *Code changes requested*, *Pull request merged*, and *Auto-archive items*. Each sets a field value — in practice `Status`. Wire them so the card moves as a consequence of work: opened → Todo, review requested → In review, merged → Done. That covers most boards with zero YAML. When it does not — cross-organisation projects, conditional logic, setting several fields at once, or backfilling — add a workflow step using GitHub's `actions/add-to-project`, or call the GraphQL mutations `addProjectV2ItemById` and `updateProjectV2ItemFieldValue` directly. Note the token: a project owned by an organisation is outside the repository's `GITHUB_TOKEN` scope, so those calls need an App installation token or a fine-grained token with project access.

code

yaml · 13 lines
yaml
name: Track bugs on the platform board
on:
  issues:
    types: [opened, labeled]
jobs:
  add:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/add-to-project@v1
        with:
          project-url: https://github.com/orgs/octo-org/projects/7
          github-token: ${{ secrets.PROJECT_TOKEN }}
          labeled: bug

go deeper

for a junior

Know that a GitHub Project has built-in workflows you switch on — item added, item closed, pull request merged — and that they set a field such as Status without any YAML.

for a middle

Name the specific rules and what each reacts to, and explain the chain where a closing keyword closes the issue on merge and the item-closed rule then moves the card.

for a senior

Show the operational detail: auto-add so nothing is forgotten, auto-archive so the board stays readable, one source of truth for status, and the token scoping that breaks org-level project writes from a repository workflow.

for a principal

Decide how much process to encode at all. Every automated transition is a claim about how work really flows; wrong claims train people to distrust the board faster than no automation would.

## Why automate at all A board that requires manual dragging is accurate for about two days. The value of a project board is that it answers "what is actually in flight" without a stand-up, and that only holds if state changes as a *side effect* of the work — opening a pull request, getting an approval, merging. Every hand-move is a chance for the board and reality to diverge, and once they diverge people stop trusting the board, which makes them move cards even less. ## Layer one: built-in workflows Each project has a *Workflows* section listing event-driven rules you enable individually. The ones that matter: - **Auto-add to project** — bind a repository and a filter (for example `is:issue is:open label:bug`); matching items are added automatically as they appear. This is the rule that removes "someone forgot to add it to the board" as a failure mode. - **Item added to project** — set `Status` to `Todo` so nothing sits with an empty status. - **Item reopened** — pull it back out of `Done`. - **Item closed** — set `Status` to `Done`. - **Code review approved** / **Code changes requested** — move a pull request between review states. - **Pull request merged** — set `Status` to `Done`. - **Auto-archive items** — archive items matching a filter (typically closed and untouched for a while) so the board does not grow without bound. These are configured per project, not per repository, and they cost nothing to run. Start here, and only escalate when a rule genuinely cannot be expressed. ## Layer two: the linked-issue effect Automation gets much better when pull requests and issues are actually linked. A pull request whose description says `Fixes #482` closes the issue on merge into the default branch — and *that close* is an event the **Item closed** workflow reacts to. So the issue card moves to Done because the code merged, without any rule that knows about pull requests at all. Chasing broken links is often higher-leverage than adding more automation. ## Layer three: Actions and the API Built-in workflows are deliberately simple: one event, one field, one value. When you need more, two options: **`actions/add-to-project`** — a GitHub-maintained action that adds the current issue or pull request to a project, with optional label filters. Useful for org-level projects fed from many repositories under conditions the built-in filter cannot state. **The GraphQL API directly** — `addProjectV2ItemById` to add, `updateProjectV2ItemFieldValue` to set a field. You must first resolve the project id, the field id, and (for single-select) the option id; none of the human-readable names work as arguments. This is the route for backfills, for setting several fields from one event, and for anything conditional on data outside GitHub. ## The token trap This is the detail that separates people who have done it from people who have read about it. The repository's automatic `GITHUB_TOKEN` is scoped to the repository. An **organisation-owned project is not in that scope**, so a workflow using `GITHUB_TOKEN` to add an item to an org project fails with a permission error even though the same workflow can write to issues happily. The fixes are a GitHub App installation token with project permissions, or a fine-grained personal access token granted organisation project access, stored as a secret. Expect to be asked why the workflow "works locally with my PAT but not in CI". ## Failure modes to name - **Boards without an auto-add rule** drift because items only appear when someone remembers. - **Two sources of truth** — a `status:` label set *and* a `Status` field, updated by different automations — guarantee disagreement. Pick one. - **Done is not archived** and the board becomes unreadable after a quarter; the auto-archive workflow exists for exactly this. - **Manual drags fighting automation**: if a rule sets `Status` on merge, a human who moves the card earlier will see it moved again. Decide which transitions are human (Todo → In progress) and which are mechanical, and do not automate the human ones. ## The honest limit Project automation is event-driven and best-effort, not a workflow engine. It cannot enforce an order of states, it will not retroactively fix items added before a rule existed, and it has no notion of "this card has been in review for nine days". Reporting like that comes from querying the project through GraphQL, not from the built-in rules.

  • A workflow using GITHUB_TOKEN fails to add an item to an organisation project. Why?
    `GITHUB_TOKEN` is scoped to the repository the workflow runs in, and an organisation-owned project is outside that scope. Use a GitHub App installation token with project permissions, or a fine-grained token granted organisation project access, stored as a secret — the same workflow will keep working for issue and pull request writes either way.
  • Which transitions should stay manual on a board?
    The ones that encode a human decision rather than a system event. Todo to In progress means someone chose to start; no event represents that honestly, and inferring it from a first commit is usually wrong. Automate the mechanical edges — added, review approved, merged, closed — and leave intent to people.
  • How do you stop a long-lived board from becoming unreadable?
    Enable the auto-archive workflow with a filter such as closed items untouched for thirty days. Archived items leave the views but stay queryable, so history is intact while the board only shows live work. Without it, Done grows without bound and every view needs a hand-written filter to compensate.
  • Why does linking a pull request to an issue improve board accuracy?
    A pull request with a closing keyword closes its issue on merge into the default branch, and that close is what the Item closed workflow reacts to. The issue card then moves to Done as a consequence of shipping, with no rule that knows anything about pull requests. Fixing broken links often beats adding automation.

saying these in an interview costs you the question

  • Writes custom Actions for what built-in workflows already do
  • Assumes GITHUB_TOKEN can write to an org-level project
  • Keeps both status labels and a Status field authoritative
  • Expects a new rule to backfill existing items
  • Automates the transition that means a human started work

context