GitHub Projects, Roadmaps, Custom Fields, Views, Automation, and Planning: Guided Hands-On Workflow and Core Operations
Create a disposable release-planning Project, add real Issues, a pull request, and a draft item, define Priority and Iteration data, build table/board/roadmap views, configure one safe built-in workflow, and verify state through gh plus API inspection.
Learning objectives
- Create a fresh public repository and a separate user-owned Project without depending on paid organization features.
- Add real issue/PR items plus a draft item and distinguish their resource identities.
- Define Priority and Iteration planning data, then build table, board, and roadmap views for different questions.
- Configure a safe built-in closed-item → Done workflow and verify the resulting field transition.
-
Use
gh projectand the current versioned Projects REST API for before/after evidence. - Clean up only disposable Project/repository resources and document what each deletion affects.
1. Scenario: Atlas 0.6 release planning
You will create atlas-c06-planning-lab with two Issues
and one small pull request, plus a user-owned Project named
Atlas 0.6 Planning Lab. The Project will contain those
records and one draft item. Priority answers “what matters most?”,
Status answers “where is the planning flow?”, and Iteration answers
“when are we planning to work on it?”
2. Preflight: identity, collisions, and credential scope
gh auth status --active --hostname github.com
OWNER=$(gh api user --jq .login)
REPO="atlas-c06-planning-lab"
PROJECT_TITLE="Atlas 0.6 Planning Lab"
gh repo view "$OWNER/$REPO" >/dev/null 2>&1 && {
echo "STOP: repository already exists" >&2; exit 2;
}
gh project list --owner "$OWNER" --format json
If the final command reports insufficient Project authorization,
inspect the active account first. On a personal disposable identity,
use gh auth refresh -s project to add the scope. This
is a credential-permission change, not a harmless
formatting option. Do not broaden an employer-managed token for a
course lab.
gh auth refresh -s project grants the CLI token Project
access. Run it only on the intended personal lab account, then
review gh auth status.
3. Create source work: repository, issues, and a pull request
gh repo create "$OWNER/$REPO" --public --add-readme --clone
cd "$REPO"
DEFAULT_BRANCH=$(git branch --show-current)
ISSUE_A=$(gh issue create --title "Release: document rollback check" --body "Add one rollback verification step to the release checklist.")
ISSUE_B=$(gh issue create --title "Bug: retry counter missing from output" --body "Synthetic bug for Project planning practice.")
# Create a tiny PR so the Project contains both Issue and PR resources.
git switch -c docs/c06-release-note
printf '
## 0.6 planning lab
Synthetic release note.
' >> README.md
git add README.md
git commit -m "docs: add 0.6 planning note"
git push -u origin docs/c06-release-note
PR_URL=$(gh pr create --base "$DEFAULT_BRANCH" --head docs/c06-release-note --title "docs: add 0.6 planning note" --body "Synthetic PR for Chapter 06.")
printf 'issueA=%s
issueB=%s
pr=%s
' "$ISSUE_A" "$ISSUE_B" "$PR_URL"
The issues and PR are source resources. Their URLs are stable identities you can add to a Project. No Project exists yet, so none of these records has Priority/Iteration planning data.
4. Create the Project and prove its identity
PROJECT=$(gh project create --owner "$OWNER" --title "$PROJECT_TITLE" --format json --jq '.number')
test -n "$PROJECT"
printf 'projectNumber=%s
' "$PROJECT"
gh project view "$PROJECT" --owner "$OWNER" --format json
# Independent read through the current versioned Projects REST API.
gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2026-03-10" "/users/$OWNER/projectsV2/$PROJECT" --jq '{number,title,state,public}'
The create command returns structured JSON, so the script captures the new Project number directly instead of guessing from a title search. The following versioned REST GET is an independent read of that exact Project resource. For user-owned Project REST endpoints, verify the endpoint token-compatibility table; GitHub currently notes that some fine-grained/App token types are not accepted even when similar organization endpoints support them.
5. Link the Project to the repository for discoverability
gh project link "$PROJECT" --owner "$OWNER" --repo "$REPO"
gh repo view "$OWNER/$REPO" --json projectsV2 --jq '.projectsV2.nodes[] | {number,title,url}'
Linking does not move issue data and does not make the Project repository-owned. It makes a same-owner Project discoverable from the repository. Repository access and Project access remain distinct controls.
6. Add issue/PR items and one draft item
gh project item-add "$PROJECT" --owner "$OWNER" --url "$ISSUE_A"
gh project item-add "$PROJECT" --owner "$OWNER" --url "$ISSUE_B"
gh project item-add "$PROJECT" --owner "$OWNER" --url "$PR_URL"
gh project item-create "$PROJECT" --owner "$OWNER" --title "Draft: decide maintenance-window wording" --body "Planning-only idea; no repository issue yet."
gh project item-list "$PROJECT" --owner "$OWNER" --limit 100 --format json
The first three items point to existing engineering records. The draft item exists only in the Project. If you delete the draft item, no repository issue is affected; if you remove a linked issue item from the Project, the issue itself still exists.
7. Add a Priority field with the CLI
gh project field-create "$PROJECT" --owner "$OWNER" --name "Priority" --data-type SINGLE_SELECT --single-select-options "P0,P1,P2"
gh project field-list "$PROJECT" --owner "$OWNER" --format json
gh project item-edit "$PROJECT" --owner "$OWNER" --url "$ISSUE_A" --field "Priority" --value "P1"
gh project item-edit "$PROJECT" --owner "$OWNER" --url "$ISSUE_B" --field "Priority" --value "P0"
gh project item-edit "$PROJECT" --owner "$OWNER" --url "$PR_URL" --field "Priority" --value "P2"
Current gh project field-create supports TEXT,
SINGLE_SELECT, DATE, and NUMBER fields. It does not expose Iteration
creation as a field-create data type, so the next step uses the
current web UI for Iteration rather than pretending the CLI has a
flag that it does not.
8. Create Iteration in the web UI, then verify it
-
Open the Project with
gh project view "$PROJECT" --owner "$OWNER" --web. - In a table view, add a new field named Iteration and choose the Iteration field type.
- Use a short lab cadence such as one week. GitHub creates the initial iteration windows automatically.
- Assign the two Issues and PR to the current or next iteration. Leave the draft unassigned to make missing scheduling visible.
gh project field-list "$PROJECT" --owner "$OWNER" --format json
gh api -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2026-03-10" "/users/$OWNER/projectsV2/$PROJECT/fields?per_page=100" --paginate
The REST field response exposes the iteration field and its configuration, including iteration IDs/date windows. This is strong evidence that the planning cadence is Project state, not a label hidden in an issue body.
9. Build three saved views with one model
- Table — Planning inventory: show Title, Status, Priority, Iteration, Assignees, Repository. Sort by Priority and use it for data-quality inspection.
- Board — Flow: group by Status. The board should answer what is Todo/In Progress/Done, not “which card looks important.”
- Roadmap — Release horizon: use Iteration as the time axis and display Priority. The draft without an Iteration should remain visibly unscheduled rather than silently disappearing from every view.
Save each view with a purpose-oriented name. A view is a query/presentation contract. If the roadmap filters out items with no Iteration, create a companion “Unscheduled” view or a filter that makes missing scheduling visible.
10. Configure one safe built-in workflow and observe causality
Open the Project menu → Workflows. Select the workflow that reacts when an issue or pull request is closed, edit it so the Project Status becomes Done, then Save and turn it on. Current GitHub documentation says close/merge-to-Done workflows are enabled by default in newly initialized Projects, but inspect the actual state rather than assuming.
# Before closing: capture both source and planning state.
gh issue view "$ISSUE_A" --json number,title,state,url
gh project item-list "$PROJECT" --owner "$OWNER" --limit 100 --field Status --field Priority --field Iteration --format json
# Trigger the source event.
gh issue close "$ISSUE_A" --comment "Closing to test Project workflow."
# Re-inspect source and Project state.
gh issue view "$ISSUE_A" --json number,title,state,url
gh project item-list "$PROJECT" --owner "$OWNER" --limit 100 --field Status --field Priority --field Iteration --format json
Causality should read: issue close event → built-in Project workflow → Status becomes Done. The Project field did not close the issue. That direction is deliberately easier to reason about and recover from.
11. Restore the lab issue and make planning state explicit
gh issue reopen "$ISSUE_A" --comment "Reopened after workflow verification."
gh project item-edit "$PROJECT" --owner "$OWNER" --url "$ISSUE_A" --field "Status" --value "Todo"
gh project item-list "$PROJECT" --owner "$OWNER" --limit 100 --field Status --field Priority --field Iteration --format json
Reopening the issue is source-state recovery. Resetting Project Status is planning-state recovery. Treat them as two explicit operations unless you have a documented workflow that synchronizes both directions.
12. Challenge: choose the correct surface
| Need | Correct surface/control | Why |
|---|---|---|
| Change which release window work is planned for | Project Iteration field | Planning schedule, not issue category |
| Prove code has merged | Pull request state / merge commit | Engineering source of truth |
| See all P0 work regardless of Status | Project filter/view | Cross-cutting planning query |
| Classify “bug” inside repository workflows | Repository label | Repository-level reusable categorization |
| Track an idea before selecting a repository | Draft Project item | No issue resource is justified yet |
13. Cleanup and rollback
First decide whether you want to preserve screenshots/JSON evidence outside the disposable resources. Then remove the Project before deleting the repository so you can observe the independent lifecycles.
cd ..
printf 'delete project=%s owner=%s
' "$PROJECT" "$OWNER"
gh project delete "$PROJECT" --owner "$OWNER"
printf 'delete repo=%s/%s
' "$OWNER" "$REPO"
gh repo delete "$OWNER/$REPO" --yes
rm -rf "$REPO"
14. Lesson summary
You built one Project from real source records, added planning-only fields, rendered three views, and observed one built-in workflow. The important result is not the board; it is the verified separation between Project state and engineering state.
Knowledge check
Why did the lab create Iteration in the UI instead of
gh project field-create?
The current CLI field-create command supports text, single-select, date, and number—not Iteration creation.
What survives if you remove an issue item from a Project?
The underlying issue survives; only Project membership/planning data is removed.
What was the authoritative trigger in the workflow test?
Closing the issue. The built-in workflow then updated Project Status to Done.
Why is the unscheduled draft useful in the roadmap exercise?
It demonstrates that a view/filter can hide planning gaps unless the team deliberately exposes missing field values.
Why are Project and repository deletion separate warnings?
They delete different resource domains and evidence; one is not a substitute for the other.
Authoritative references
Creating a project
About iteration fields
Changing the layout of a view
Built-in Project automations
gh project
gh project create
gh project field-create
gh project item-edit
gh project item-list
gh project link
REST API for Projects
REST Project field endpoints
REST API versions
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.