gh Projects Tutorial
As I have gotten back into coding I’ve been using a GitHub project to manage all my tasks. Project support in the gh cli isn’t the best user experience, requiring boilerplate and esoteric knowledge. Here is how to make it easier.
Setup#
Ensure you have the GitHub CLI installed and are authenticated.
# MacOS with homebrew
brew install gh
# Authenticate with GitHub, follow the prompts
gh auth login
# Add Project permissions
gh auth refresh -s project
Find the project owner and ID. In the case of mine at https://github.com/users/theantichris/projects/4 the owner is theantichris the ID is 4.
GitHub CLI requires the owner and ID for every command so I created a zsh function to cut out the boilerplate.
# In ~/.zshrc
# Replace with your ID and GitHub username. $0 passes in any arguments
ghp() {
gh project "$@" 4 --owner theantichris
}
For the rest of this article I’ll be using the ghp zsh function. If you’d rather use it without replace it with gh project <command> <project-id> --owner <project-owner>.
Add Items#
You can create a new issue using the cli.
# -R specifies the repo, -t title, and -b body
gh issue create -R theantichris/example-repo -t "New feature: add user settings" -b "Need to implement user settings page"
You can then add the issue to the project.
ghp item-add --url https://github.com/theantichris/example-repo/issues/123
The item-add command can be used to add pull requests also.
Listing Items#
The cli has a list-item command that lists all issues, draft issues, and pull requests in the project. It defaults to 30 items but can be overridden with the -L or --limit flag.
ghp item-list

Moving Items#
To move a card to “In Progress” you need to update its Status field value. This is where it starts getting complicated. The cli expects the underlying field ID of the status, one does not simply use “In Progress”.
The easiest way to do this is using the field-list --format=json command and find the object for Status.
ghp field-list --format=json

In my case the Status field ID is PVTSSF_lAHOABaups4BDqWezg1g6b0 and the ID for the “In progress” status is 47fc9ee4.
You can get the items ID from the list-item command.
You will also need the project node ID which you can find with the view --format=json command.
ghp view --format=json

Now put it all together using item-edit. For some reason this command doesn’t take the normal project ID and --owner flag.
gh project item-edit --id PVTI_lAHOABaups4BDqWezggGjAk --project-id PVT_kwHOABaups4BDqWe --field-id PVTSSF_lAHOABaups4BDqWezg1g6b0 --single-select-option-id 47fc9ee4

Let’s make another zsh function that makes this easier.
# Replace the IDs with the ones from your project
ghpm() {
local item_id=$1
local status_name=$2
local project_id="PVT_kwHOABaups4BDqWe"
local field_id="PVTSSF_lAHOABaups4BDqWezg1g6b0"
local status_id
case "$status_name" in
"Backlog") status_id="f75ad846" ;;
"Ready") status_id="61e4505c" ;;
"In progress") status_id="47fc9ee4" ;;
"In review") status_id="df73e18b" ;;
"Done") status_id="98236657" ;;
*)
echo "❌ Unknown status: $status_name"
echo "Valid options: Backlog, Ready, In progress, In review, Done"
return 1
;;
esac
echo "→ Moving item $item_id to '$status_name'..."
gh project item-edit --id "$item_id" --project-id "$project_id" --field-id "$field_id" --single-select-option-id "$status_id"
}
