github.nvim is a comprehensive GitHub REST API client for Neovim,
written in Lua. It provides both synchronous and asynchronous APIs
for interacting with GitHub's REST API, covering issues, pull requests,
releases, actions, secrets, rulesets, search, users, and more.
Inspired by github.vim, it brings GitHub interaction directly into your editor with a clean and composable module-based design.
- ✨ Features
- 📦 Installation
- 🔧 Configuration
- ⚡ Sync vs Async
- 📚 API Reference
- 🧩 Chat.nvim Integration
- ❓ FAQ
- 🙏 Credits
- 📣 Self-Promotion
- 📄 License
- Full GitHub REST API coverage across 9 modules
- Both synchronous and asynchronous APIs for every function
- Async powered by job.nvim with callback support
- Secure token handling — write-only, never exposed publicly
- GitHub Enterprise support via configurable base URL
- 147 tests with CI on Neovim nightly/stable across Ubuntu, Windows, and macOS
- Integrates with chat.nvim as a built-in tool
github.nvim works with all major Neovim plugin managers. Neovim 0.10+ is recommended for best compatibility.
-
Using nvim-plug
require('plug').add({ { 'wsdjeg/github.nvim', config = function() require('github').setup({ token = 'your-github-token', }) end, }, })
-
Using lazy.nvim
{ 'wsdjeg/github.nvim', config = function() require('github').setup({ token = 'your-github-token', }) end, } -
Using packer.nvim
use({ 'wsdjeg/github.nvim', config = function() require('github').setup({ token = 'your-github-token', }) end, })
-
Using luarocks
luarocks install github.nvimThen in your config:
require('github').setup({ token = 'your-github-token', })
require('github').setup({
-- GitHub personal access token (required)
-- Token is stored internally and cannot be read back via any public API
token = 'your-github-token',
-- Base URL for GitHub API
-- Use this for GitHub Enterprise: 'https://github.example.com/api/v3/'
base_url = 'https://api.github.com/',
})| Option | Type | Default | Description |
|---|---|---|---|
token |
string | nil |
GitHub personal access token (write-only) |
base_url |
string | 'https://api.github.com/' |
API base URL, change for GitHub Enterprise |
Security: The token is stored in a private local variable inside
util.lua. No public function or variable exposes it. It can only be set viasetup().
Every API function comes in two flavors:
| Sync | Async | |
|---|---|---|
| Returns | table (parsed JSON) |
integer (job_id) |
| Blocking? | Yes — uses vim.fn.systemlist |
No — uses job.nvim |
| Naming | M.get(user, repo, id) |
M.get_async(user, repo, id, callbacks, opts) |
local callbacks = {
on_success = function(id, data, http_code) end, -- 2xx response
on_error = function(id, err, http_code?) end, -- non-2xx or error
on_exit = function(id, code, signal) end, -- always called
}
local opts = { timeout = 30000 } -- optional, millisecondslocal pulls = require('github').pulls
pulls.list_async('wsdjeg', 'github.nvim', 'open', {
on_success = function(id, data, http_code)
for _, pr in ipairs(data) do
print(pr.number, pr.title)
end
end,
on_error = function(id, err)
vim.notify('Failed: ' .. err, vim.log.levels.ERROR)
end,
})Most API functions require user (owner) and repo (repository name) as
the first two arguments. Each sync function has an _async counterpart with
the same parameters plus callbacks and opts.
local M = require('github').issues
M.get(user, repo, id)
M.create_issue(user, repo, issue_data)
M.update_issue(user, repo, id, issue_data)
-- Async (add _async suffix + callbacks + opts)
M.get_async(user, repo, id, callbacks, opts)
M.create_issue_async(user, repo, issue_data, callbacks, opts)
M.update_issue_async(user, repo, id, issue_data, callbacks, opts)local M = require('github').pulls
M.list(user, repo, state) -- state: 'open', 'closed', 'all'
M.get(user, repo, pull_number)
M.create(user, repo, params)
M.update(user, repo, pull_number, params)
M.merge(user, repo, pull_number, params)
M.create_review(user, repo, pull_number, params)
M.list_reviews(user, repo, pull_number)
M.list_commits(user, repo, pull_number)
M.list_files(user, repo, pull_number)
M.check_merge_status(user, repo, pull_number)local M = require('github').repository
M.update(user, repo, repository_data)local M = require('github').releases
M.list(user, repo)
M.get_by_id(user, repo, release_id)
M.get_latest(user, repo)
M.get_by_tag(user, repo, tag)
M.create(user, repo, params)
M.update(user, repo, release_id, params)
M.delete(user, repo, release_id)
M.list_assets(user, repo, release_id)
M.upload_asset(user, repo, release_id, file_path, name, label)
M.delete_asset(user, repo, asset_id)local M = require('github').actions
M.list_workflows(user, repo)
M.get_workflow(user, repo, workflow_id)
M.list_workflow_runs(user, repo, params)
M.get_workflow_run(user, repo, run_id)
M.re_run_workflow(user, repo, run_id)
M.cancel_workflow_run(user, repo, run_id)
M.list_jobs_for_run(user, repo, run_id)
M.download_job_logs(user, repo, job_id, output)
M.get_job_logs(user, repo, job_id)
M.list_artifacts(user, repo)
M.get_artifact(user, repo, artifact_id)
M.delete_artifact(user, repo, artifact_id)local M = require('github').secrets
M.list_repository_secrets(user, repo)
M.delete_repository_secret(user, repo, secret_name)
M.get_repository_secrets_public_key(user, repo)
M.update_repository_secret(user, repo, secret)Note:
update_repository_secretrequires luasodium for encrypting secret values with the repository's public key.
local M = require('github').rulesets
M.get_repository_rules(user, repo)
M.get_branch_rules(user, repo, branch)
M.create_ruleset(user, repo, ruleset)
M.get_repository_ruleset(user, repo, id)
M.update_ruleset(user, repo, id, ruleset)
M.delete_ruleset(user, repo, id)
M.get_ruleset_history(user, repo, id)
M.get_ruleset_version(user, repo, id, version)local M = require('github').search
M.repositories(query, params)
M.code(query, params)
M.issues(query, params)
M.users(query, params)
M.commits(query, params)local M = require('github').users
-- Users
M.get_user(username)
M.get_authenticated_user()
M.update_user(params)
M.list_followers(username)
M.list_following(username)
M.list_repos(username, params)
-- Organizations
M.get_org(org)
M.update_org(org, params)
M.list_members(org, params)
M.list_org_repos(org, params)github.nvim integrates with chat.nvim as a built-in tool, allowing the AI assistant to interact with GitHub Actions directly from the chat window.
Supported operations:
| Operation | Description |
|---|---|
list_workflows |
List all workflows in a repository |
get_workflow |
Get a specific workflow |
list_workflow_runs |
List workflow runs with filters |
get_workflow_run |
Get a specific workflow run |
list_jobs_for_run |
List jobs for a workflow run |
download_job_logs |
Download logs for a job (zip) |
get_job_logs |
Get parsed job logs (steps) |
list_artifacts |
List artifacts for a repository |
get_artifact |
Get a specific artifact |
re_run_workflow |
Re-run a workflow |
cancel_workflow_run |
Cancel a workflow run |
delete_artifact |
Delete an artifact |
How do I get a GitHub token?
- Go to GitHub Settings → Developer settings → Personal access tokens
- Generate a new token with the required scopes (e.g.,
repo,workflow) - Pass it to
setup({ token = 'ghp_xxxx' })
Can I use environment variables for the token?
No. As of v0.2.0, the token is set exclusively via setup(). This ensures
the token is stored securely in a private local variable and cannot be
read back through any public API.
Does it support GitHub Enterprise?
Yes. Set base_url in setup():
require('github').setup({
token = 'your-token',
base_url = 'https://github.example.com/api/v3/',
})- wsdjeg/Github.vim — the original Vim plugin
Like this plugin? Star the repository on GitHub.
Love this plugin? Follow me on GitHub.
This project is licensed under the GPL-3.0 License.