GitHub’s gh CLI can be enhanced via custom extensions. The following offers an introduction, as well as some notes and tips for doing so in Go.
- How do gh extensions work?
- Implementation tips, suggestions, etc.
- Bonus experimental idea: bootstrapping developer experience and platform engineering
- Further reading
Out of the box, the gh CLI supports a collection of commands for interacting with GitHub features like repositories, releases, pull requests, and more. For example, to view a repository’s open pull requests, use
gh pr ls:
gh can be installed via common package managers (
brew install gh on Mac OS). It’s also preinstalled on all GitHub-hosted Actions runners and available for use in GitHub Actions CI/CD pipelines, as demonstrated by mdb/ensure-unpublished-release-action’s release pipeline, which invokes
gh release create to publish GitHub releases.
Beyond its built-in features,
gh can be extended to support custom commands. I recently created the gh-dispatch extension for triggering repository_dispatch and/or workflow_dispatch events and watching the resulting GitHub Actions workflow run. Once installed, the extension provides a
gh dispatch command:
gh extension install installs extensions from their GitHub repositories. To install
gh extension install mdb/gh-dispatch
…and to view all installed extensions, use
gh extension list:
gh actions-importer github/gh-actions-importer v1.0.1 gh actions-status rsese/gh-actions-status v1.0.0 gh dash dlvhdr/gh-dash v3.4.1 gh dispatch mdb/gh-dispatch 0.0.1 gh markdown-preview yusukebe/gh-markdown-preview 198c536b gh metrics hectcastro/gh-metrics v2.1.0
A few extensions of note include…
- gh-dash - a Terminal dashboard for GitHub issues and pull requests
- gh-install - install GitHub release binaries
- gh-changelog - create keep a changelog-style changelogs
To see other community-maintained
gh extensions, browse GitHub repositories tagged with the “gh-extension” topic.
(And if you’ve got improvement ideas for
gh-dispatch, open a PR!)
How do gh extensions work?
Under the hood,
gh CLI extensions are
gh--prefixed executables located at a standard path (sidebar: this is similar to how kubectl plugins work too). For example, on Mac OS, the executables live in
ls ~/.local/share/gh/extensions gh-actions-importer gh-dispatch gh-metrics gh-actions-status gh-dash gh-markdown-preview
With this in mind, a rudimentary extension might be a simple
bash script named and located accordingly:
cat ~/.local/share/gh/extensions/gh-hello/gh-hello #!/bin/bash echo "hello"
…and thereby executable as a
gh hello hello
So, returning to the gh-dispatch example…
gh extension install mdb/gh-dispatch fetches the
gh-dispatch extension from GitHub and, at least in the case of Mac OS, installs it to the
~/.local/share/extensions/gh-dispatch path. Because
gh-dispatch’s own build process precompiles the extension for various OS/architecture combos and publishes the resulting binaries to a GitHub release,
gh extension install knows to download the correct binary associated with the targeted release version (by default, it fetches the latest release, though specific versions can be targeted with a
(Arguably, all this becomes especially compelling when considering the growing ubiquity of GitHub, the widespread popularity of the
gh CLI, and that
gh extension install requires no additional package managers, local runtimes, or developer environment setup, at least in the case of precompiled extensions like gh-dispatch. So, with this in mind, perhaps a custom
gh extension could be leveraged as a sensible entrypoint when bootsrapping higher level developer experiences for an organization – think platform onboarding, environment setup, repository generation, secrets management, build utilities, etc. But, more on all that later.)
Implementation tips, suggestions, etc.
gh extension can be authored in any language, Go is an especially good fit for a few reasons:
ghCLI itself is authored in Go.
ghitself is written in Go using the Cobra framework, its source code offers lotsa helpful examples and patterns. For example, the
gh pr lsimplementation reveals how
ghsubcommands are declared as a &cobra.Command.
Furthermore, the packages homed in https://github.com/cli/cli/ can also be imported by extension source code, which enables helpful reuse patterns. As an example,
gh-dispatchleverages upstream https://github.com/cli/cli/ packages, particularly when rendering output. A few other packages of note include…
- github.com/cli/cli/v2/api - a GitHub API client.
- github.com/cli/cli/v2/pkg/httpmock - utilities for mocking HTTP transactions during automated tests (see gh-dispatch’s own tests for example usage).
- github.com/cli/cli/v2/pkg/iostreams - utilities for working with IO and rendering CLI extension output.
Disclaimer: I’m not actually sure the
ghmaintainers intend for these packages to be used outside the CLI codebase, but it can be done.
github.com/cli/go-gh can be used by Go-based extensions.
In addition to the https://github.com/cli/cli/ packages,
github.com/cli/go-ghis also available to Go-based extension developers. According to
go-ghis a collection of Go modules to make authoring GitHub CLI extensions easier.
Go programs can be easily precompiled across OS/architecture combos.
By precompiling a CLI extension, users can install and run the extension without additional run time dependencies (i.e. bash, Ruby, Python, etc.) beyond the
ghCLI itself, nor is it necessary to provide users any per-OS/per-architecture installation instructions;
gh extension install <OWNER>/<REPO>simply downloads the appropriate binary from the
<OWNER>/<REPO>GitHub releases (For example, note the precompiled binaries associated with each of gh-dispatch’s GitHub releases).
Also helpful: gh-extension-precompile is a reusable GitHub Action for automating the publication of such binaries to GitHub releases, saving Go-based extension authors from writing their own CI/CD automation. However, if you’d prefer to write your own release automation – or to use goreleaser directly, as gh-dispatch’s release process does – that’s not too hard, either (just make sure the precompiled binaries are named correctly;
gh extension installassumes a naming convention. When in doubt, emulate a known working example, such as gh-dispatch).
Bonus experimental idea: bootstrapping developer experience and platform engineering
gh extensions can be useful in and of themselves (I use gh-dash every day!), the ecosystem teases some broader possibilities: could a
gh extension sensibly serve as the cornerstone of an organization’s developer platform experience?
In my experience, it’s common for engineering leadership to fantasize about a cohesive internal developer platform, often facilitated via a purpose-built CLI, web portal, and/or API, and enabling developer efficiency via stuff like…
- a standard interface to centralized automation
- automated software project setup and repository scaffolding
- the generation and maintenance of standard, templated CI/CD pipelines
- the maintenance of source code repository best practices, such as standard required status checks and required PR code review approvals
- per-team public cloud provider account provisioning and infrastructure configuration
- sensible management of role-based access control, secrets (such as API credentials), adherence to the principle of least privilege and similar security-sensitive safeguards, etc.
- a common interface to otherwise disparate tools, such as documentation, project management, dashboards, etc.
- a general purpose mechanism for self-service and the discovery of ever-evolving internal platform capabilities
However, depending on context (org size, engineering practices, priorities, etc.), building such a developer platform involves labor and complexity that may jeopardize more business-critical efforts, especially when doing so invests in proprietary solutions to common needs. To varying degrees, open tools like Backstage and various Internal Developer Platforms aim to help. Zooming out, even Kubernetes itself – despite its complexity – aspires to be an open, common, standard-ish platform. Technologies like Kubevela, Argo, and Flux further enhance the Kubernetes platform experience, each in their own way. These are promising tools. But, perhaps building on the ubiquity of the existing GitHub ecosystem via a custom
gh extension might be a relatively low-effort, low-risk compliment to your organization’s developer experience as well? Or maybe a sensible entrypoint before tools beyond GitHub are implicated?
(In my experience, an organization-specific
gh extension becomes especially attractive when it serves as an interface to other commonly-used GitHub features: GitHub Actions workflows, infrastructure-as-code automation, GitHub pages-hosted JSON endpoints, GitHub secrets, GitHub repository environments, GitHub template repositories, GitHub teams, etc. From this perspective, perhaps GitHub itself emerges as a practical foundation to a robust internal developer platform ecosystem, with a custom
gh extension tying it all together as a cohesive user experience.)
A big disclaimer, though: all this
gh-extension-facilitated developer experience talk is bit experimental and largely intended as food for thought. Context matters in nuanced ways, do your own thinkin’/assessin’, your mileage may vary, maybe YAGNI, etc. Plus, even in absence of big developer experience ambitions, the
gh CLI and its community-maintained extensions have clear small-scale utility.