:octopus: Octo.nvim

May 18, 2026 ยท View on GitHub

GitHub repository size Issues License Say thanks Latest commit Repository's starts

:octopus: Octo.nvim

Edit and review GitHub issues, pull requests, and discussions from the comfort of your favorite editor.

Just edit the title, body, or comments as a regular buffer and use :w(rite) to sync with GitHub.

BuyMeACoffee

๐ŸŒฒ Table of Contents

๐Ÿ’ซ Features

  • Edit GitHub issues, PRs, and discussions
  • Add/Modify/Delete comments
  • Add/Remove label, reactions, assignees, project cards, reviewers, etc.
  • Add Review PRs
  • Interact with GitHub CLI from lua with octo.gh module.

๐Ÿ”ฅ Examples

Octo https://github.com/pwntester/octo.nvim/issues/12
Octo issue create
Octo issue create pwntester/octo.nvim
Octo comment add
Octo reaction add hooray
Octo issue edit pwntester/octo.nvim 1
Octo issue edit 1
Octo issue list createdBy=pwntester
Octo issue list neovim/neovim labels=bug,help\ wanted states=OPEN
Octo search assignee:pwntester is:pr
Octo search is:discussion repo:pwntester/octo.nvim category:"Show and Tell"

You can pass GitHub URLs directly to the Octo command, including GitHub Enterprise URLs:

" GitHub.com URLs
Octo https://github.com/pwntester/octo.nvim/issues/12
Octo https://github.com/pwntester/octo.nvim/pull/123

" GitHub Enterprise URLs (hostname is automatically detected)
Octo https://ghe.example.com/owner/repo/issues/456
Octo https://ghe.example.com/owner/repo/pull/789

You can also use octo:// URLs to open issues and PRs directly:

" Open from the default GitHub instance (github.com or configured github_hostname)
:e octo://owner/repo/issue/123
:e octo://owner/repo/pull/456

" Open from a specific GitHub Enterprise instance
:e octo://ghe.example.com/owner/repo/issue/123
:e octo://ghe.example.com/owner/repo/pull/456

" Both singular and plural forms are supported
:e octo://owner/repo/issues/123
:e octo://owner/repo/pulls/456

The octo:// URL format is especially useful for:

  • Opening issues/PRs from notes or wiki links without needing to be in the repository directory
  • Working with multiple GitHub instances (e.g., GitHub.com and GitHub Enterprise) without setting GH_HOST globally
  • Creating quick links in your workflow that work regardless of your current directory

From any octo buffer, press <CR> in normal mode to see common actions.

๐ŸŽฏ Requirements

After installation, run :checkhealth octo to verify your setup.

๐Ÿ“ฆ Installation

For a basic installation using lazy.nvim, try:

{
  "pwntester/octo.nvim",
  cmd = "Octo",
  opts = {
    -- or "fzf-lua" or "snacks" or "default"
    picker = "telescope",
    -- bare Octo command opens picker of commands
    enable_builtin = true,
  },
  keys = {
    {
      "<leader>oi",
      "<CMD>Octo issue list<CR>",
      desc = "List GitHub Issues",
    },
    {
      "<leader>op",
      "<CMD>Octo pr list<CR>",
      desc = "List GitHub PullRequests",
    },
    {
      "<leader>od",
      "<CMD>Octo discussion list<CR>",
      desc = "List GitHub Discussions",
    },
    {
      "<leader>on",
      "<CMD>Octo notification list<CR>",
      desc = "List GitHub Notifications",
    },
    {
      "<leader>os",
      function()
        require("octo.utils").create_base_search_command { include_current_repo = true }
      end,
      desc = "Search GitHub",
    },
  },
  dependencies = {
    "nvim-lua/plenary.nvim",
    "nvim-telescope/telescope.nvim",
    -- OR "ibhagwan/fzf-lua",
    -- OR "folke/snacks.nvim",
    "nvim-tree/nvim-web-devicons", -- optional if file_panel.icons is a function
  },
}

๐Ÿ”ง Configuration

Below is the full default configuration for octo.nvim.

require"octo".setup {
  picker = "telescope", -- or "fzf-lua" or "snacks" or "default"
  picker_config = {
    use_emojis = false, -- only used by "fzf-lua" picker for now
    search_static = true, -- Whether to use static search results (true) or dynamic search (false)
    mappings = { -- mappings for the pickers
      open_in_browser = { lhs = "<C-b>", desc = "open issue in browser" },
      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
      copy_sha = { lhs = "<C-e>", desc = "copy commit SHA to system clipboard" },
      checkout_pr = { lhs = "<C-o>", desc = "checkout pull request" },
      merge_pr = { lhs = "<C-r>", desc = "merge pull request" },
    },
    snacks = { -- snacks specific config
      -- Initialize actions as empty arrays
      actions = { -- custom actions for specific snacks pickers (array of tables)
        issues = { -- actions for the issues picker
          -- { name = "my_issue_action", fn = function(picker, item) print("Issue action:", vim.inspect(item)) end, lhs = "<leader>a", desc = "My custom issue action" },
        },
        pull_requests = { -- actions for the pull requests picker
          -- { name = "my_pr_action", fn = function(picker, item) print("PR action:", vim.inspect(item)) end, lhs = "<leader>b", desc = "My custom PR action" },
        },
        notifications = {}, -- actions for the notifications picker
        issue_templates = {}, -- actions for the issue templates picker
        search = {}, -- actions for the search picker
        -- ... add actions for other pickers as needed
        changed_files = {},
        commits = {},
        review_commits = {},
      },
    },
  },
  default_remote = { "upstream", "origin" }, -- order to try remotes
  default_merge_method = "merge", -- default merge method which should be used for both `Octo pr merge` and merging from picker, could be `merge`, `rebase` or `squash`
  default_delete_branch = false, -- whether to delete branch when merging pull request with either `Octo pr merge` or from picker (can be overridden with `delete`/`nodelete` argument to `Octo pr merge`)
  ssh_aliases = {}, -- SSH aliases. e.g. `ssh_aliases = {["github.com-work"] = "github.com"}`. The key part will be interpreted as an anchored Lua pattern.
  reaction_viewer_hint_icon = "๏‘„ ", -- marker for user reactions
  commands = {}, -- additional subcommands made available to `Octo` command
  users = "search", -- Users for assignees or reviewers. Values: "search" | "mentionable" | "assignable"
  user_icon = "๏Šฝ ", -- user icon
  ghost_icon = "๓ฐŠ  ", -- ghost icon
  copilot_icon = "๏’ธ ", -- copilot icon
  dependabot_icon = "๏’พ ",
  comment_icon = "โ–Ž",
  outdated_icon = "๓ฐ…’ ",
  resolved_icon = "๏€Œ ",
  timeline_marker = "๏‘  ",
  timeline_indent = 2,
  use_timeline_icons = true,
  timeline_icons = {
    auto_squash = "๏‡  ",
    blocking = "๏‘น  ",
    commit_push = "๏ƒ  ",
    comment_deleted = "๏’Ž  ",
    duplicate = "๏“„  ",
    force_push = "๏ƒ  ",
    draft = "๏“  ",
    ready = "๏‘ ",
    commit = "๏’ถ  ",
    deployed = "๏ง  ",
    issue_type = "๏›  ",
    label = "๏’  ",
    reference = "๏—  ",
    project = "๏”ฅ  ",
    connected = "๏‘ก  ",
    subissue = "๏“ฉ  ",
    cross_reference = "๏’ฝ  ",
    transferred = "๏‘ฅ  ",
    parent_issue = "๏“จ  ",
    head_ref = "๏˜  ",
    pinned = "๏ต  ",
    milestone = "๏‘  ",
    renamed = "๏‘ˆ  ",
    automatic_base_change_succeeded = "๏˜  ",
    base_ref_changed = "๏˜  ",
    merged = { "๏™  ", "OctoPurple" },
    closed = {
      closed = { "๏“œ  ", "OctoRed" },
      completed = { "๏’ž  ", "OctoPurple" },
      not_planned = { "๏‘จ  ", "OctoWhite" },
      duplicate = { "๏‘จ  ", "OctoWhite" },
    },
    reopened = { "๏‡  ", "OctoGreen" },
    assigned = "๏•  ",
    locked = "๏‘–  ",
    review_requested = "๏‘  ",
  },
  right_bubble_delimiter = "๎‚ด", -- bubble delimiter
  left_bubble_delimiter = "๎‚ถ", -- bubble delimiter
  github_hostname = "", -- GitHub Enterprise host
  use_local_fs = false, -- use local files on right side of reviews
  enable_builtin = false, -- shows a list of builtin actions when no action is provided
  snippet_context_lines = 4, -- number of lines around commented lines
  gh_cmd = "gh", -- Command to use when calling Github CLI
  gh_env = {}, -- extra environment variables to pass on to GitHub CLI, can be a table or function returning a table
  timeout = 5000, -- timeout for requests between the remote server
  default_to_projects_v2 = false, -- use projects v2 for the `Octo card ...` command by default. Both legacy and v2 commands are available under `Octo cardlegacy ...` and `Octo cardv2 ...` respectively.
  suppress_missing_scope = {
    projects_v2 = false,
  },
  ui = {
    use_signcolumn = false, -- show "modified" marks on the sign column
    use_statuscolumn = true, -- show "modified" marks on the status column
    use_foldtext = true,
  },
  issues = {
    order_by = { -- criteria to sort results of `Octo issue list`
      field = "CREATED_AT", -- either COMMENTS, CREATED_AT or UPDATED_AT (https://docs.github.com/en/graphql/reference/enums#issueorderfield)
      direction = "DESC", -- either DESC or ASC (https://docs.github.com/en/graphql/reference/enums#orderdirection)
    },
  },
  discussions = {
    order_by = {
      field = "CREATED_AT",
      direction = "DESC",
    },
  },
  notifications = {
    current_repo_only = false, -- show notifications for current repo only
  },
  reviews = {
    auto_show_threads = true, -- automatically show comment threads on cursor move
    focus = "right", -- focus right buffer on diff open
  },
  runs = {
    icons = {
      pending = "๐Ÿ•–",
      in_progress = "๐Ÿ”„",
      failed = "โŒ",
      succeeded = "",
      skipped = "โฉ",
      cancelled = "โœ–",
    },
  },
  pull_requests = {
    order_by = { -- criteria to sort the results of `Octo pr list`
      field = "CREATED_AT", -- either COMMENTS, CREATED_AT or UPDATED_AT (https://docs.github.com/en/graphql/reference/enums#issueorderfield)
      direction = "DESC", -- either DESC or ASC (https://docs.github.com/en/graphql/reference/enums#orderdirection)
    },
    always_select_remote_on_create = false, -- always give prompt to select base remote repo when creating PRs
    use_branch_name_as_title = false, -- sets branch name to be the name for the PR
  },
  file_panel = {
    size = 10, -- changed files panel rows
    icons = true, -- true = nvim-web-devicons, false = disabled, function = custom provider
  },
  colors = { -- used for highlight groups (see Colors section below)
    white = "#ffffff",
    grey = "#2A354C",
    black = "#000000",
    red = "#fdb8c0",
    dark_red = "#da3633",
    green = "#acf2bd",
    dark_green = "#238636",
    yellow = "#d3c846",
    dark_yellow = "#735c0f",
    blue = "#58A6FF",
    dark_blue = "#0366d6",
    purple = "#6f42c1",
  },
  mappings_disable_default = false, -- disable default mappings if true, but will still adapt user mappings
  mappings = {
    discussion = {
      discussion_options = { lhs = "<CR>", desc = "show discussion options" },
      open_in_browser = { lhs = "<C-b>", desc = "open discussion in browser" },
      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
      add_comment = { lhs = "<localleader>ca", desc = "add comment" },
      add_reply = { lhs = "<localleader>cr", desc = "add reply" },
      delete_comment = { lhs = "<localleader>cd", desc = "delete comment" },
      comment_edits = { lhs = "<localleader>ce", desc = "show comment edit history" },
      reference_in_new_issue = { lhs = "<localleader>ri", desc = "reference comment in new issue" },
      add_label = { lhs = "<localleader>la", desc = "add label" },
      remove_label = { lhs = "<localleader>ld", desc = "remove label" },
      next_comment = { lhs = "]c", desc = "go to next comment" },
      prev_comment = { lhs = "[c", desc = "go to previous comment" },
      react_hooray = { lhs = "<localleader>rp", desc = "add/remove ๐ŸŽ‰ reaction" },
      react_heart = { lhs = "<localleader>rh", desc = "add/remove โค๏ธ reaction" },
      react_eyes = { lhs = "<localleader>re", desc = "add/remove ๐Ÿ‘€ reaction" },
      react_thumbs_up = { lhs = "<localleader>r+", desc = "add/remove ๐Ÿ‘ reaction" },
      react_thumbs_down = { lhs = "<localleader>r-", desc = "add/remove ๐Ÿ‘Ž reaction" },
      react_rocket = { lhs = "<localleader>rr", desc = "add/remove ๐Ÿš€ reaction" },
      react_laugh = { lhs = "<localleader>rl", desc = "add/remove ๐Ÿ˜„ reaction" },
      react_confused = { lhs = "<localleader>rc", desc = "add/remove ๐Ÿ˜• reaction" },
    },
    runs = {
      expand_step = { lhs = "o", desc = "expand workflow step" },
      next_step = { lhs = "]s", desc = "next workflow step" },
      prev_step = { lhs = "[s", desc = "previous workflow step" },
      next_job = { lhs = "]j", desc = "next workflow job" },
      prev_job = { lhs = "[j", desc = "previous workflow job" },
      open_in_browser = { lhs = "<C-b>", desc = "open workflow run in browser" },
      refresh = { lhs = "<C-r>", desc = "refresh workflow" },
      rerun = { lhs = "<C-o>", desc = "rerun workflow" },
      rerun_failed = { lhs = "<C-f>", desc = "rerun failed workflow" },
      cancel = { lhs = "<C-x>", desc = "cancel workflow" },
      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
    },
    issue = {
      issue_options = { lhs = "<CR>", desc = "show issue options" },
      close_issue = { lhs = "<localleader>ic", desc = "close issue" },
      reopen_issue = { lhs = "<localleader>io", desc = "reopen issue" },
      list_issues = { lhs = "<localleader>il", desc = "list open issues on same repo" },
      reload = { lhs = "<C-r>", desc = "reload issue" },
      open_in_browser = { lhs = "<C-b>", desc = "open issue in browser" },
      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
      add_assignee = { lhs = "<localleader>aa", desc = "add assignee" },
      remove_assignee = { lhs = "<localleader>ad", desc = "remove assignee" },
      create_label = { lhs = "<localleader>lc", desc = "create label" },
      add_label = { lhs = "<localleader>la", desc = "add label" },
      remove_label = { lhs = "<localleader>ld", desc = "remove label" },
      goto_issue = { lhs = "<localleader>gi", desc = "navigate to a local repo issue" },
      add_comment = { lhs = "<localleader>ca", desc = "add comment" },
      add_reply = { lhs = "<localleader>cr", desc = "add reply" },
      delete_comment = { lhs = "<localleader>cd", desc = "delete comment" },
      comment_edits = { lhs = "<localleader>ce", desc = "show comment edit history" },
      reference_in_new_issue = { lhs = "<localleader>ri", desc = "reference comment in new issue" },
      next_comment = { lhs = "]c", desc = "go to next comment" },
      prev_comment = { lhs = "[c", desc = "go to previous comment" },
      react_hooray = { lhs = "<localleader>rp", desc = "add/remove ๐ŸŽ‰ reaction" },
      react_heart = { lhs = "<localleader>rh", desc = "add/remove โค๏ธ reaction" },
      react_eyes = { lhs = "<localleader>re", desc = "add/remove ๐Ÿ‘€ reaction" },
      react_thumbs_up = { lhs = "<localleader>r+", desc = "add/remove ๐Ÿ‘ reaction" },
      react_thumbs_down = { lhs = "<localleader>r-", desc = "add/remove ๐Ÿ‘Ž reaction" },
      react_rocket = { lhs = "<localleader>rr", desc = "add/remove ๐Ÿš€ reaction" },
      react_laugh = { lhs = "<localleader>rl", desc = "add/remove ๐Ÿ˜„ reaction" },
      react_confused = { lhs = "<localleader>rc", desc = "add/remove ๐Ÿ˜• reaction" },
    },
    pull_request = {
      pr_options = { lhs = "<CR>", desc = "show PR options" },
      checkout_pr = { lhs = "<localleader>po", desc = "checkout PR" },
      merge_pr = { lhs = "<localleader>pm", desc = "merge commit PR" },
      squash_and_merge_pr = { lhs = "<localleader>psm", desc = "squash and merge PR" },
      rebase_and_merge_pr = { lhs = "<localleader>prm", desc = "rebase and merge PR" },
      merge_pr_queue = {
        lhs = "<localleader>pq",
        desc = "merge commit PR and add to merge queue (Merge queue must be enabled in the repo)",
      },
      squash_and_merge_queue = {
        lhs = "<localleader>psq",
        desc = "squash and add to merge queue (Merge queue must be enabled in the repo)",
      },
      rebase_and_merge_queue = {
        lhs = "<localleader>prq",
        desc = "rebase and add to merge queue (Merge queue must be enabled in the repo)",
      },
      list_commits = { lhs = "<localleader>pc", desc = "list PR commits" },
      list_changed_files = { lhs = "<localleader>pf", desc = "list PR changed files" },
      show_pr_diff = { lhs = "<localleader>pd", desc = "show PR diff" },
      add_reviewer = { lhs = "<localleader>va", desc = "add reviewer" },
      remove_reviewer = { lhs = "<localleader>vd", desc = "remove reviewer request" },
      close_issue = { lhs = "<localleader>ic", desc = "close PR" },
      reopen_issue = { lhs = "<localleader>io", desc = "reopen PR" },
      list_issues = { lhs = "<localleader>il", desc = "list open issues on same repo" },
      reload = { lhs = "<C-r>", desc = "reload PR" },
      approve_pr = { lhs = "<leader>qa", desc = "approve PR" },
      open_in_browser = { lhs = "<C-b>", desc = "open PR in browser" },
      copy_url = { lhs = "<C-y>", desc = "copy url to system clipboard" },
      copy_sha = { lhs = "<C-e>", desc = "copy commit SHA to system clipboard" },
      goto_file = { lhs = "gf", desc = "go to file" },
      add_assignee = { lhs = "<localleader>aa", desc = "add assignee" },
      remove_assignee = { lhs = "<localleader>ad", desc = "remove assignee" },
      create_label = { lhs = "<localleader>lc", desc = "create label" },
      add_label = { lhs = "<localleader>la", desc = "add label" },
      remove_label = { lhs = "<localleader>ld", desc = "remove label" },
      goto_issue = { lhs = "<localleader>gi", desc = "navigate to a local repo issue" },
      add_comment = { lhs = "<localleader>ca", desc = "add comment" },
      add_reply = { lhs = "<localleader>cr", desc = "add reply" },
      delete_comment = { lhs = "<localleader>cd", desc = "delete comment" },
      comment_edits = { lhs = "<localleader>ce", desc = "show comment edit history" },
      reference_in_new_issue = { lhs = "<localleader>ri", desc = "reference comment in new issue" },
      next_comment = { lhs = "]c", desc = "go to next comment" },
      prev_comment = { lhs = "[c", desc = "go to previous comment" },
      react_hooray = { lhs = "<localleader>rp", desc = "add/remove ๐ŸŽ‰ reaction" },
      react_heart = { lhs = "<localleader>rh", desc = "add/remove โค๏ธ reaction" },
      react_eyes = { lhs = "<localleader>re", desc = "add/remove ๐Ÿ‘€ reaction" },
      react_thumbs_up = { lhs = "<localleader>r+", desc = "add/remove ๐Ÿ‘ reaction" },
      react_thumbs_down = { lhs = "<localleader>r-", desc = "add/remove ๐Ÿ‘Ž reaction" },
      react_rocket = { lhs = "<localleader>rr", desc = "add/remove ๐Ÿš€ reaction" },
      react_laugh = { lhs = "<localleader>rl", desc = "add/remove ๐Ÿ˜„ reaction" },
      react_confused = { lhs = "<localleader>rc", desc = "add/remove ๐Ÿ˜• reaction" },
      review_start = { lhs = "<localleader>vs", desc = "start a review for the current PR" },
      review_resume = { lhs = "<localleader>vr", desc = "resume a pending review for the current PR" },
      resolve_thread = { lhs = "<localleader>rt", desc = "resolve PR thread" },
      unresolve_thread = { lhs = "<localleader>rT", desc = "unresolve PR thread" },
    },
    review_thread = {
      goto_issue = { lhs = "<localleader>gi", desc = "navigate to a local repo issue" },
      add_comment = { lhs = "<localleader>ca", desc = "add comment" },
      add_reply = { lhs = "<localleader>cr", desc = "add reply" },
      add_suggestion = { lhs = "<localleader>sa", desc = "add suggestion" },
      delete_comment = { lhs = "<localleader>cd", desc = "delete comment" },
      comment_edits = { lhs = "<localleader>ce", desc = "show comment edit history" },
      reference_in_new_issue = { lhs = "<localleader>ri", desc = "reference comment in new issue" },
      next_comment = { lhs = "]c", desc = "go to next comment" },
      prev_comment = { lhs = "[c", desc = "go to previous comment" },
      select_next_entry = { lhs = "]q", desc = "move to next changed file" },
      select_prev_entry = { lhs = "[q", desc = "move to previous changed file" },
      select_first_entry = { lhs = "[Q", desc = "move to first changed file" },
      select_last_entry = { lhs = "]Q", desc = "move to last changed file" },
      select_next_unviewed_entry = { lhs = "]u", desc = "move to next unviewed file" },
      select_prev_unviewed_entry = { lhs = "[u", desc = "move to previous unviewed file" },
      close_review_tab = { lhs = "<C-c>", desc = "close review tab" },
      react_hooray = { lhs = "<localleader>rp", desc = "add/remove ๐ŸŽ‰ reaction" },
      react_heart = { lhs = "<localleader>rh", desc = "add/remove โค๏ธ reaction" },
      react_eyes = { lhs = "<localleader>re", desc = "add/remove ๐Ÿ‘€ reaction" },
      react_thumbs_up = { lhs = "<localleader>r+", desc = "add/remove ๐Ÿ‘ reaction" },
      react_thumbs_down = { lhs = "<localleader>r-", desc = "add/remove ๐Ÿ‘Ž reaction" },
      react_rocket = { lhs = "<localleader>rr", desc = "add/remove ๐Ÿš€ reaction" },
      react_laugh = { lhs = "<localleader>rl", desc = "add/remove ๐Ÿ˜„ reaction" },
      react_confused = { lhs = "<localleader>rc", desc = "add/remove ๐Ÿ˜• reaction" },
      resolve_thread = { lhs = "<localleader>rt", desc = "resolve PR thread" },
      unresolve_thread = { lhs = "<localleader>rT", desc = "unresolve PR thread" },
    },
    submit_win = {
      approve_review = { lhs = "<C-a>", desc = "approve review", mode = { "n" } },
      comment_review = { lhs = "<C-m>", desc = "comment review", mode = { "n" } },
      request_changes = { lhs = "<C-r>", desc = "request changes review", mode = { "n" } },
      close_review_tab = { lhs = "<C-c>", desc = "close review tab", mode = { "n" } },
    },
    review_diff = {
      submit_review = { lhs = "<localleader>vs", desc = "submit review" },
      discard_review = { lhs = "<localleader>vd", desc = "discard review" },
      add_review_comment = { lhs = "<localleader>ca", desc = "add a new review comment", mode = { "n", "x" } },
      add_review_suggestion = { lhs = "<localleader>sa", desc = "add a new review suggestion", mode = { "n", "x" } },
      focus_files = { lhs = "<localleader>e", desc = "move focus to changed file panel" },
      toggle_files = { lhs = "<localleader>b", desc = "hide/show changed files panel" },
      next_thread = { lhs = "]t", desc = "move to next thread" },
      prev_thread = { lhs = "[t", desc = "move to previous thread" },
      select_next_entry = { lhs = "]q", desc = "move to next changed file" },
      select_prev_entry = { lhs = "[q", desc = "move to previous changed file" },
      select_first_entry = { lhs = "[Q", desc = "move to first changed file" },
      select_last_entry = { lhs = "]Q", desc = "move to last changed file" },
      select_next_unviewed_entry = { lhs = "]u", desc = "move to next unviewed file" },
      select_prev_unviewed_entry = { lhs = "[u", desc = "move to previous unviewed file" },
      close_review_tab = { lhs = "<C-c>", desc = "close review tab" },
      toggle_viewed = { lhs = "<localleader><space>", desc = "toggle viewer viewed state" },
      goto_file = { lhs = "gf", desc = "go to file" },
      copy_sha = { lhs = "<C-e>", desc = "copy commit SHA to system clipboard" },
      review_commits = { lhs = "<localleader>C", desc = "review PR commits" },
    },
    file_panel = {
      submit_review = { lhs = "<localleader>vs", desc = "submit review" },
      discard_review = { lhs = "<localleader>vd", desc = "discard review" },
      next_entry = { lhs = "j", desc = "move to next changed file" },
      prev_entry = { lhs = "k", desc = "move to previous changed file" },
      select_entry = { lhs = "<cr>", desc = "show selected changed file diffs" },
      refresh_files = { lhs = "R", desc = "refresh changed files panel" },
      focus_files = { lhs = "<localleader>e", desc = "move focus to changed file panel" },
      toggle_files = { lhs = "<localleader>b", desc = "hide/show changed files panel" },
      select_next_entry = { lhs = "]q", desc = "move to next changed file" },
      select_prev_entry = { lhs = "[q", desc = "move to previous changed file" },
      select_first_entry = { lhs = "[Q", desc = "move to first changed file" },
      select_last_entry = { lhs = "]Q", desc = "move to last changed file" },
      select_next_unviewed_entry = { lhs = "]u", desc = "move to next unviewed file" },
      select_prev_unviewed_entry = { lhs = "[u", desc = "move to previous unviewed file" },
      close_review_tab = { lhs = "<C-c>", desc = "close review tab" },
      toggle_viewed = { lhs = "<localleader><space>", desc = "toggle viewer viewed state" },
      review_commits = { lhs = "<localleader>C", desc = "review PR commits" },
    },
    notification = {
      read = { lhs = "<localleader>nr", desc = "mark notification as read" },
      done = { lhs = "<localleader>nd", desc = "mark notification as done" },
      unsubscribe = { lhs = "<localleader>nu", desc = "unsubscribe from notifications" },
    },
    repo = {
      repo_options = { lhs = "<CR>", desc = "show repo options" },
      create_issue = { lhs = "<localleader>ic", desc = "create issue" },
      create_discussion = { lhs = "<localleader>dc", desc = "create discussion" },
      contributing_guidelines = { lhs = "<localleader>cg", desc = "view contributing guidelines" },
      open_in_browser = { lhs = "<C-b>", desc = "open repo in browser" },
    },
    release = {
      open_in_browser = { lhs = "<C-b>", desc = "open release in browser" },
    },
  },
  poll = {
    enabled = false, -- opt-in polling for remote changes
    interval = 10000, -- polling interval in milliseconds (default: 10s)
    notify_on_refresh = true, -- notify when a buffer is auto-refreshed
    notify_on_change = true, -- notify when remote changed but buffer has local edits
  },
  search = {
    completion_overrides = {}, -- key is a qualifier, value is an array table or a function returning a table
  },
  debug = {
    notify_missing_timeline_items = false,
  },
}

File panel icons

Configure file panel icons with file_panel.icons:

  • true: use nvim-web-devicons
  • false: disable icons
  • function(name, ext): return icon, hl
require("octo").setup({
  file_panel = {
    icons = function(name, _ext)
      return require("mini.icons").get("file", name)
    end,
  },
})

๐Ÿค– Commands

There is only an Octo <object> <action> [arguments] command: If no command is passed, the argument to Octo is treated as a URL from where an issue or pr repo and number are extracted.

ObjectActionArguments
issuecloseClose the current issue
reopenReopen the current issue
create [repo]Creates a new issue in the current or specified repo
developCreate and checkout a new branch for an issue in the current repo
edit [repo]Edit issue <number> in current or specified repo
list [repo] [key=value] (1)List all issues satisfying given filter
searchLive issue search
reloadReload issue. Same as doing e!
browserOpen current issue in the browser
urlCopies the URL of the current issue to the system clipboard
subscriptionChange subscription state (subscribe, unsubscribe, or ignore)
pinPin the current issue
unpinUnpin the current issue
prlist [repo] [key=value] (2)List all PRs satisfying given filter
searchLive issue search
edit [repo]Edit PR <number> in current or specified repo
reopenReopen the current PR
createCreates a new PR for the current branch
closeClose the current PR
checkoutCheckout PR
commitsList all PR commits
changesShow all PR changes (diff hunks)
diffShow PR diff
merge [merge|rebase|squash] [delete|nodelete]Merge current PR using the specified method
readyMark a draft PR as ready for review
draftSend a ready PR back to draft
checksShow the status of all checks run on the PR
reloadReload PR. Same as doing e!
browserOpen current PR in the browser
urlCopies the URL of the current PR to the system clipboard
subscriptionChange subscription state (subscribe, unsubscribe, or ignore)
shaCopies the head commit SHA of the current PR to the system clipboard
runsList all workflow runs for the PR
repolist (3)List repos user owns, contributes or belong to
forkFork repo
browserOpen current repo in the browser
urlCopies the URL of the current repo to the system clipboard
subscriptionChange subscription state (subscribe, unsubscribe, or ignore)
viewOpen a repo by path ({organization}/{name})
gistlist [repo] [key=value] (4)List user gists
commentaddAdd a new comment
suggestAdd a new suggestion
deleteDelete a comment
urlCopies the URL of the current comment to the system clipboard
replyAdd comment as a reply to the current comment
threadresolveMark a review thread as resolved
unresolveMark a review thread as unresolved
labeladd [label]Add a label from available label menu
remove [label]Remove a label
create [label]Create a new label
delete [label]Delete an existing label from repo
edit [label]Edit name or description of an existing label from repo
typeaddAdd an issue type from available issue type menu
removeRemove an issueType
milestoneadd [milestone]Add a milestone to current Issue or PR
removeRemove a milestone from current Issue or PR
create [milestone]Create a new milestone
list [repo]List all milestones for repo or current repo
assigneeadd [login]Assign a user
remove [login]Unassign a user
revieweradd [login]Assign a PR reviewer
reactionthumbs_up | +1Add ๐Ÿ‘ reaction
thumbs_down | -1Add ๐Ÿ‘Ž reaction
eyesAdd ๐Ÿ‘€ reaction
laughAdd ๐Ÿ˜„ reaction
confusedAdd ๐Ÿ˜• reaction
rocketAdd ๐Ÿš€ reaction
heartAdd โค๏ธ reaction
hooray | party | tadaAdd ๐ŸŽ‰ reaction
cardaddAssign issue/PR to a project new card
removeDelete project card
moveMove project card to different project/column
reviewstartStart a new review
submitSubmit the review
resumeEdit a pending review for current PR
discardDeletes a pending review for current PR if any
commentsView pending review comments
commitPick a specific commit to review
closeClose the review window and return to the PR
actionsLists all available Octo actions
searchSearch GitHub for issues and PRs matching the query or Discussions with is:discussion
runlistList workflow runs
notificationlistShows current unread notifications
discussionlist [repo]List open discussions for current or specified repo
edit [repo]Edit discussion in current or specified repo
browserOpen the current discussion in the browser
create [repo]Create discussion for current or specified repo
reloadReload the current discussion buffer
closeClose the discussion
markMark the discussion comment as answer
unmarkUnmark the discussion comment as answer
reopenReopen the current discussion
searchSearch discussions
subscriptionChange subscription state (subscribe, unsubscribe, or ignore)
categoryChange category of discussion
parentaddAdd a parent issue to current issue
removeRemove the parent issue to current issue
editEdit the parent issue to current issue
childaddAdd a child issue to current issue
releasenotesGenerate release notes in current buffer
list [repo]List release notes for current or specified repo
pollstartStart polling tracked buffers for remote changes
stopStop polling
toggleToggle polling on/off
statusShow polling status and tracked buffers
applyForce-reload a dirty buffer with pending remote changes
  1. [repo]: If repo is not provided, it will be derived from <cwd>/.git/config.

  2. In-menu mappings:

  • <CR>: Edit Issue
  • <C-b>: Opens issue in the browser
  • <C-y>: Copies URL to system clipboard
  • <C-e>: Copies commit SHA to system clipboard (where applicable)

Available filter keys

  • since
  • createdBy
  • assignee
  • mentioned
  • labels
  • milestone
  • states
  1. In-menu mappings:
  • <CR>: Edit PR
  • <C-b>: Opens PR in the browser
  • <C-o>: Checkout PR
  • <C-y>: Copies URL to system clipboard
  • <C-e>: Copies commit SHA to system clipboard (for commits and PRs)

Available filter keys

  • baseRefName
  • headRefName
  • labels
  • states
  1. In-menu mappings:
  • <CR>: View repo
  • <C-b>: Opens repo in the browser
  • <C-y>: Copies URL to system clipboard
  1. In-menu mappings:
  1. Users in the assignee and reviewer commands:
  • search: Dynamically search all GitHub users

  • mentionable: List of mentionable users in current repo

  • assignable: List of assignable users in current repo

    Here, search is the default value and most broad. Both assignable and mentionable are specific to the current repo. The assignable option is more restrictive than mentionable.

๐Ÿ“‹ PR reviews

  • Enter review mode for the current branch with Octo review. Alternatively open the PR (e.g. Octo <PR url> or Octo pr list or Octo pr edit <PR number>) then use Octo review in the PR buffer to enter review mode for a specific PR.
  • A new tab will show a panel with changed files and two windows showing the diff on any of them.
  • Change panel entries with ]q and [q or by selecting an entry in the window
  • Add comments with <localleader>ca or suggestions with <localleader>sa on single or multiple visual-selected lines
    • A new buffer will appear in the alternate diff window. The cursor will be positioned in the new buffer
    • When ready, save the buffer to commit changes to GitHub
    • Move back to the diff window and move the cursor, the thread buffer will hide
  • Hold the cursor on a line with a comment to show a thread buffer with all the thread comments
    • To modify, delete, react or reply to a comment, move to the window containing the thread buffer
    • Perform any operations as if you were in a regular issue buffer
  • Review pending comments with Octo review comments
    • Use to jump to the selected pending comment
  • If you want to review a specific commit, use Octo review commit to pick a commit. The file panel will get filtered to show only files changed by that commit. Any comments placed on these files will be applied at that specific commit level and will be added to the pending review.
  • When ready, submit the review with Octo review submit
  • A new float window will pop up. Enter the top level review comment and exit to normal mode. Then press <C-m> to submit a comment, <C-a> to approve it or <C-r> to request changes

๐Ÿž Completion

Octo provides a built-in omnifunc completion for issues, PRs and users that you can trigger using <C-x><C-o>. Alternately, if you use nvim-cmp or blink.cmp for completion, you can use the cmp-git or blink-cmp-git source to provide issues, PRs, commits and users completion.

Also, you can use cmp-emoji or blink-emoji.nvim to get markdown emoji completion.

๐ŸŽจ Colors

Highlight GroupLinked To
OctoNormalNormal
OctoCursorLineCursorLine
OctoWinSeparatorWinSeparator
OctoSignColumnNormal
OctoStatusColumnSignColumn
OctoStatusLineStatusLine
OctoStatusLineNCStatusLineNC
OctoEndOfBufferEndOfBuffer
OctoFilePanelFileNameNormalFloat
OctoFilePanelSelectedFileType
OctoFilePanelPathComment
OctoStatusAddedOctoGreen
OctoStatusUntrackedOctoGreen
OctoStatusModifiedOctoBlue
OctoStatusRenamedOctoBlue
OctoStatusCopiedOctoBlue
OctoStatusTypeChangeOctoBlue
OctoStatusUnmergedOctoBlue
OctoStatusUnknownOctoYellow
OctoStatusDeletedOctoRed
OctoStatusBrokenOctoRed
OctoDirtyOctoRed
OctoIssueIdNormalFloat
OctoIssueTitlePreProc
OctoFloatNormalFloat
OctoTimelineItemHeadingComment
OctoTimelineMarkerIdentifier
OctoSymbolComment
OctoDateComment
OctoDetailsLabelTitle
OctoDetailsValueIdentifier
OctoMissingDetailsComment
OctoEmptyNormalFloat
OctoBubbleNormalFloat
OctoUserOctoBubble
OctoUserViewerOctoViewer
OctoReactionOctoBubble
OctoReactionViewerOctoViewer
OctoPassingTestOctoGreen
OctoFailingTestOctoRed
OctoPullAdditionsOctoGreen
OctoPullDeletionsOctoRed
OctoPullModificationsOctoGrey
OctoStateOpenOctoGreen
OctoStateClosedOctoRed
OctoStateCompletedOctoPurple
OctoStateNotPlannedOctoGrey
OctoStateDraftOctoGrey
OctoStateMergeOctoPurple
OctoStatePendingOctoYellow
OctoStateApprovedOctoGreen
OctoStateChangesRequestedOctoRed
OctoStateDismissedOctoRed
OctoStateCommentedOctoBlue
OctoStateSubmittedOctoGreen
OctoStateOpenBubbleOctoBubbleGreen
OctoStateClosedBubbleOctoBubbleRed
OctoStateMergedBubbleOctoBubblePurple
OctoStatePendingBubbleOctoBubbleYellow
OctoStateApprovedBubbleOctoBubbleGreen
OctoStateChangesRequestedBubbleOctoBubbleRed
OctoStateDismissedBubbleOctoBubbleRed
OctoStateCommentedBubbleOctoBubbleBlue
OctoStateSubmittedBubbleOctoBubbleGreen
OctoStateOpenFloatOctoGreenFloat
OctoStateClosedFloatOctoRedFloat
OctoStateMergedFloatOctoPurpleFloat
OctoStateDraftFloatOctoGreyFloat
OctoReviewDiffDeleteTextOctoRed
OctoReviewDiffAddTextOctoGreen

The term GitHub color refers to the colors used in the WebUI. The (addition) viewer means the user of the plugin or more precisely the user authenticated via the gh CLI tool used to retrieve the data from GitHub.

Completion for :Octo search arguments can be overridden for each specific qualifier using search.completion_overrides config option. Example usecase: disable completion functionality that requires remote calls to GitHub server.

๐Ÿ“บ Demos

issues

prs

๐Ÿ™‹ FAQ

I get a warning saying Cannot request projects v2, missing scope 'read:project'

That's expected. The new support for projects v2 support requires the read:project scope on your GitHub token.

You add the scope by using gh auth refresh -s read:project or you can suppress this warning by setting the following in your config

{
  suppress_missing_scope = {
    projects_v2 = true,
  }
}

How can I disable bubbles for XYZ?

Each text-object that makes use of a bubble (except labels) do use their own highlight group that links per default to the main bubble highlight group. To disable most bubbles at once you can simply link OctoBubble to Normal. To only disable them for a certain plain do the same for the specific sub-group (e.g. OctoUser).

Why do my issue titles or markdown syntax do not get highlighted properly?

The title, body and comments of an issue or PR are special as they get special highlighting applied and is an editable section. Due to the latter property it gets the OctoEditable highlighting via a special signs linehl setting. This takes precedence over the buffer internal highlights. To only get the background highlighted by the editable section, set OctoEditable to a highlight with a background color definition only.

Why am I getting authentication error from gh?

This means that are either using a GITHUB_TOKEN to authenticate or gh is not authenticated.

In case of the former, run:

GITHUB_TOKEN= gh auth login

... and choose a method to authorise access for gh.

gh must store the credentials so it can work in a subshell.

Why am I getting authentication error from gh? Part 2

It is possible that Octo is trying to authenticate against the origin listed in your repository's config (.git/config) which could be an ssh alias to github.com. To properly let octo.nvim know about the ssh alias, you need to list it in the config per above. Example:

require('octo').setup({
  ssh_aliases = {["<THE ALIAS YOU HAVE LISTED IN ~/.ssh/config>"] = "github.com"}
})

Can I use treesitter markdown parser with octo buffers?

Just add the following lines to your TreeSitter config:

vim.treesitter.language.register('markdown', 'octo')

How can I filter PRs by filter keys that aren't available?

You can use the search command :Octo search [query]. The search syntax and available search terms are available in GitHub documentation.

For example to search for PRs with author you can use this command:

:Octo search is:pr author:pwntester repo:github/codeql

Note: You need to provide the repo, otherwise it will search for every PR by that user.

How to enable autocompletion for issues/prs (#) and users (@)?

Add the following mappings for octo file type:

  • vim.keymap.set("i", "@", "@<C-x><C-o>", { silent = true, buffer = true })
  • vim.keymap.set("i", "#", "#<C-x><C-o>", { silent = true, buffer = true })

How can I disable default key mappings?

Set mappings_disable_default = true in user config.

๐Ÿ™Œ Contributing

Contributions are always welcome!

See CONTRIBUTING for ways to get started.

Please adhere to this project's CODE_OF_CONDUCT.

You are welcome to join the #octo.nvim:matrix.org chatroom if you need help on anything.

๐ŸŒŸ Credits

The PR review panel is heavily inspired in diffview.nvim

๐Ÿ™ Say Thanks

If you like this plugin and would like to buy me a coffee, you can!

BuyMeACoffee

GitHub Sponsors

๐Ÿ“œ License

MIT