Atlas.nvim

August 5, 2026 · View on GitHub

Neovim Version CI License

Atlas.nvim

A Neovim plugin for managing GitHub/Bitbucket/GitLab PRs and Jira/GitHub/GitLab issues without leaving your editor.

Caution

Still in early development, will have breaking changes!

GitHub Bitbucket GitLab Jira

AtlasDiff Atlas UI

Table of Contents

Installation

Using lazy.nvim

{
  "emrearmagan/atlas.nvim",
  dependencies = {
    "nvim-tree/nvim-web-devicons", -- optional but recommended
    "MeanderingProgrammer/render-markdown.nvim", -- optional but recommended
    "esmuellert/codediff.nvim", -- optional (PullRequest diff)
    "sindrets/diffview.nvim", -- optional; or "dlyongemallo/diffview-plus.nvim"
  },
  -- See Configuration below
  opts = {},
}

Using packer.nvim

use {
  "emrearmagan/atlas.nvim",
  config = function()
    -- See Configuration below
    require("atlas").setup({})
  end
}

Tip

It's a good idea to run :checkhealth atlas to see if everything is set up correctly.

Requirements

  • Neovim: 0.10+
  • git and curl on $PATH
  • Jira: Jira Cloud REST API v3 (*.atlassian.net) or Jira Server REST API v2
  • Bitbucket: Bitbucket Cloud REST API 2.0 (api.bitbucket.org)
  • GitHub: GitHub CLI (gh) authenticated with gh auth login
  • GitLab: GitLab REST API v4 (gitlab.com or self-hosted), Personal Access Token with api scope

Note

I have only tested this with my personal and work accounts. If you encounter any issues, please feel free to open an issue. See: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/

I have also not tested with self-hosted GitLab instances, but in theory it should work. If it doesn't, feel free to open an issue. If it does work, please remove this note :)

Configuration

{
  -- Too lazy to manage a statusline per split? Same. Make it global.
  global_statusline = true,

  pulls = {
    -- See Pulls Configuration below.
    providers = {
      ---@type AtlasBitbucketConfig
      bitbucket = {},
      ---@type AtlasGitHubConfig
      github = {},
      ---@type AtlasGitLabPullsConfig
      gitlab = {},
    },
  },
  issues = {
    -- See Issue Configuration below.
    providers = {
      ---@type AtlasJiraIssuesConfig
      jira = {},
      ---@type AtlasGitHubIssuesConfig
      github = {},
      ---@type AtlasGitLabIssuesConfig
      gitlab = {},
    },
  },
}

Set global_statusline = false to leave Neovim's laststatus option unchanged.

Commands

  • :AtlasIssues [provider] - Open Atlas issues domain
  • :AtlasPulls [provider] - Open Atlas pulls domain
  • :AtlasDiff <base>...<head> or :AtlasDiff <pull-request-url> - Open a local Git range or pull request review
  • :AtlasNotes - Inspect local review notes across pull requests
  • :AtlasCreatePR - Create a pull request from the current branch
  • :AtlasCreateIssue - Create an issue (GitHub / GitLab / Jira)
  • :AtlasSearch [provider] - Search configured pull-request and issue providers
  • :AtlasOpen <target> - Open a provider URL, Jira key, repository reference, or PR/issue number
  • :AtlasClearCache - Clear Atlas disk and memory cache
  • :AtlasLogs - Toggle Atlas logs

Pulls

Use :AtlasPulls [provider] to browse and manage pull requests from GitHub, Bitbucket, and GitLab.

Pulls Configuration

pulls = {
  diff = {
    -- Any command that accepts explicit <base>...<head> Git revisions.
    open_cmd = "AtlasDiff", -- default; for example "DiffviewOpen" or "CodeDiff".

    -- AtlasDiff options; external viewers use their own configuration.
    layout = "inline", -- "inline" or "side-by-side".
    compact = true, -- Start with only changed hunks and surrounding context visible.
    compact_context_lines = 3, -- Context lines shown around hunks in compact mode.
    show_review_panel = false, -- Set true to show comments and notes when AtlasDiff opens.
    explorer = {
      grouped = true, -- Group changed files by directory.
      hidden = false,
      show_commits = false, -- Set true to show commits below changed files initially.
      width = 40,
      initial_focus = "explorer", -- "explorer" or "diff".
      ignore = { ".git/**", ".jj/**" },
    },
  },
  repo_config = {
    -- Maps `workspace/repo` to local paths. Used for checkout, diffs, and custom actions.
    paths = {
      ["your-workspace/*"] = "~/code/repos/*",
      ["your-workspace/atlas"] = "~/code/atlas",
    },
    settings = {
      ["your-workspace/atlas"] = {
        readme = "README.md", -- optional, defaults to README.md
        pr_template = ".github/pull_request_template.md", -- optional, defaults to .github/pull_request_template.md
      },
    },
  },
  custom_actions = {}, -- See Custom Actions below.
},

GitHub
pulls = {
  providers = {
    github = {
      cache_ttl = 300,

      ---@type AtlasGitHubViewConfig[]
      views = {
        {
          name = "My PRs",
          key = "1",
          layout = "plain",
          search = "author:@me sort:updated-desc",
        },
        {
          name = "Team",
          key = "2",
          layout = "compact",
          search = "org:your-org sort:updated-desc",
        },
        {
          name = "Repo",
          key = "3",
          layout = "plain",
          search = "repo:your-org/your-repo",
        },
      },

      bookmarks = {
        key   = "S",      -- default
        label = "Search", -- default
        items = {
          ["Drafts"]           = "is:pr is:draft author:@me",
          ["Recently merged"]  = "is:pr is:merged author:@me sort:updated-desc",
          ["Review requested"] = "is:pr is:open review-requested:@me",
        },
      },
    },
  },
},
GitHub pull requests

Bitbucket
pulls = {
  providers = {
    bitbucket = {
      user = vim.env.BITBUCKET_USER,
      token = vim.env.BITBUCKET_TOKEN,
      cache_ttl = 300,

      ---@type AtlasBitbucketViewConfig[]
      views = {
        {
          name = "Me",
          key = "M",
          layout = "compact",
          repos = {
            { workspace = "your-workspace", repo = "atlas" },
          },

          ---@param pr PullRequest
          ---@param ctx { user: PullsUser|nil }
          filter = function(pr, ctx)
            local user = ctx.user
            return pr.author and user and pr.author.id == user.id
          end,
        },
        {
          name = "Team",
          key = "1",
          layout = "plain", -- "compact" or "plain"
          repos = {
            { workspace = "your-workspace", repo = "atlas" },
            { workspace = "your-workspace", repo = "other-repo" },
          },
        },
      },
    },
  },
},
Bitbucket pull requests

GitLab

Auth uses a Personal Access Token with the api scope. Set base_url to https://gitlab.com or your self-hosted instance.

pulls = {
  providers = {
    gitlab = {
      base_url = "https://gitlab.com",
      token = vim.env.GITLAB_TOKEN,
      cache_ttl = 300,

      ---@type AtlasGitLabPullsViewConfig[]
      views = {
        {
          name = "Assigned",
          key = "1",
          scope = "assigned_to_me",
        },
        {
          name = "Reviewing",
          key = "3",
          scope = "all",
          extra_params = { reviewer_id = "Me" },
        },
        -- Single project
        {
          name = "GitLab",
          key = "G",
          project = "gitlab-org/gitlab",
        },
        -- Whole group, all projects under it
        {
          name = "GitLab Org",
          key = "O",
          group = "gitlab-org",
        },
      },

      bookmarks = {
        key   = "S",      -- default
        label = "Search", -- default
        items = {
          ["Reviewing"]    = { scope = "all", extra_params = { reviewer_id = "Me" } },
          ["Merged by me"] = { scope = "all", state = "merged", author_username = "me" },
        },
      },
    },
  },
},
GitLab pull requests

Issues

Use :AtlasIssues [provider] to browse and manage Jira, GitHub, and GitLab issues.

Issue Configuration

issues = {
  max_results = 100,
  with_relationships = true, -- Fetch parent/subissue relationships for plain issue tree views.
  custom_actions = {}, -- See Custom Actions below.
}

Jira

Note

If you're only looking for Jira support, check out https://github.com/letieu/jira.nvim. This plugin was the main inspiration for this project. Jira support is included here mainly because I wanted a single tool that works with both Atlassian products.

Important

The markdown editor for issue descriptions and comments is still experimental and may not work perfectly in all cases. You can toggle between markdown and ADF view in the overview tab to see the raw ADF content and how it translates to markdown. If you encounter any issues with the markdown editor, please open an issue with details.

issues = {
  providers = {
    jira = {
      base_url = "https://your-site.atlassian.net",
      email = "you@example.com",
      --- See: https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
      token = "your_jira_api_token",
      auth_method = "basic", -- "basic" or "bearer", defaults to "basic". If using bearer, set `token` to your API token.
      api_type = "cloud", -- either "cloud" or "server", defaults to "cloud". Cloud API is v3, server API is v2
      cache_ttl = 300,

      project_config = {
        -- The Jira custom field ID used for story points. Defaults to "customfield_10016".
        story_points_field = "customfield_10016",
        issue_types = {
          ["Maintenance"] = { icon = "", hl_group = "AtlasTextWarning" },
          ["Infrastructure"] = { icon = "󰒋", hl_group = "AtlasLogInfo" },
        },

        KAN = {
          customfield_10003 = {
            name = "Approvers",
            format = function(value)
              if type(value) ~= "table" or #value == 0 then
                return nil -- nil hides the field
              end
              return table.concat(value, ", ")
            end,
            hl_group = "AtlasChipActive",
            display = "chip", -- "chip" or "table"
          },
        },
      },

      ---@type AtlasJiraViewConfig[]
      views = {
        {
          name = "My Board",
          key = "M",
          layout = "plain",
          jql = "project = KAN AND assignee = currentUser() ORDER BY updated DESC",
        },
        {
          name = "Team Board",
          key = "T",
          layout = "compact",
          jql = "project = KAN ORDER BY updated DESC",
        },
      },

      bookmarks = {
        key   = "J",   -- default
        label = "JQL", -- default
        items = {
          ["Backlog"]     = "project = KAN AND statusCategory != Done AND (sprint IS EMPTY OR sprint NOT IN openSprints()) ORDER BY Rank ASC",
          ["Next sprint"] = "project = KAN AND sprint in futureSprints() ORDER BY Rank ASC",
          ["My open"]     = "assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC",
        },
      },
    },
  },
},
Jira issues

GitHub Issues
issues = {
  providers = {
    github = {
      cache_ttl = 300,

      ---@type AtlasGitHubIssuesViewConfig[]
      views = {
        {
          name = "Assigned",
          key = "1",
          layout = "plain",
          search = "assignee:@me is:open",
        },
        {
          name = "Created",
          key = "2",
          layout = "compact",
          search = "author:@me is:open",
        },
        {
          name = "Mentions",
          key = "3",
          layout = "plain",
          search = "mentions:@me is:open",
        },
      },

      bookmarks = {
        key   = "S",      -- default
        label = "Search", -- default
        items = {
          ["Bugs"]            = "is:issue is:open label:bug",
          ["Recently closed"] = "is:issue is:closed author:@me sort:updated-desc",
        },
      },
    },
  },
},

GitLab Issues

Auth uses a Personal Access Token with the api scope. Set base_url to https://gitlab.com or your self-hosted instance.

issues = {
  providers = {
    gitlab = {
      base_url = "https://gitlab.com",
      token = vim.env.GITLAB_TOKEN,
      cache_ttl = 300,

      ---@type AtlasGitLabIssuesViewConfig[]
      views = {
        {
          name = "Assigned",
          key = "1",
          scope = "assigned_to_me",
          state = "opened",
        },
        {
          name = "Created",
          key = "2",
          scope = "created_by_me",
          state = "opened",
        },
        {
          name = "All open",
          key = "3",
          scope = "all",
          state = "opened",
          -- Anything not covered by the explicit fields below can be passed via `extra_params`.
          extra_params = { ["not[labels]"] = "wontfix" },
        },
      },

      bookmarks = {
        key   = "S",      -- default
        label = "Search", -- default
        items = {
          ["No labels"] = { scope = "all", state = "opened",
                            extra_params = { ["not[labels]"] = "*" } },
          ["Closed"]    = { scope = "created_by_me", state = "closed" },
        },
      },
    },
  },
},

Features

Atlas keeps the pull request and issue workflows you use throughout the day inside Neovim.

Review Pull Requests

AtlasDiff review

Press the configured pulls.open_diff key (gd by default) on a pull request to start a review.

  • See pending, resolved, and outdated provider threads at their diff locations.
  • Review provider tasks and GitHub checklists alongside the comments they belong to.
  • Add, reply to, edit, delete, resolve, or reopen comments when supported.
  • Browse provider comments and local notes in AtlasDiff's bottom list; use za to expand an item.
  • Submit pending comments with an optional review summary when supported.

Note

Alternative viewers: CodeDiff, Diffview, and Diffview-plus can display Atlas comment, task, and local-note overlays, but their integrations rely on plugin internals and may break after upstream changes.

Local notes

Local review notes

Local notes let you leave something on a diff without posting it to the pull request. Each note is attached to a file and line and can be an ISSUE, SUGGESTION, NOTE, or PRAISE. Diff views mark notes as outdated when their saved line changes.


For scripts, use bin/atlas-notes. Notes added there appear in AtlasDiff, CodeDiff, Diffview, Diffview-plus, and :AtlasNotes:

./bin/atlas-notes add \
  --target https://github.com/owner/repository/pull/123 \
  --file lua/review_queue.lua --line 19 \
  --context "local item = queue[index]" \
  --type suggestion --body "Should this be a bool?"

My dotfiles include a Pi extension that wraps this script so review agents can list and add notes.

View Pipelines

View pipelines

View pipelines and their jobs, inspect their status, and read job logs directly in Atlas. Retry failed pipelines or jobs and cancel work that is still running.


Create Pull Requests

Create pull request

:AtlasCreatePR opens the pull request form for the current branch. The newest commit supplies the title. Atlas first reads the configured pr_template, or .github/pull_request_template.md by default.

Without a template, Atlas groups conventional commits into sections, recognizes leading Jira keys such as [JIRA-123], links commit hashes and issue references, collects references under Related, and appends the diffstat. If no commits use a conventional prefix, it uses a linked plain commit list instead.

Edit the title and description, choose the target branch and reviewers, set the draft state, or preview commits and diffstat before submitting.


Create Issues

Create issue

:AtlasCreateIssue opens the creation flow for the configured issue providers. GitHub and GitLab use the current repository, while Jira uses the configured instance. The forms support Markdown descriptions and provider-specific fields such as labels, assignees, milestones, and Jira issue types.

GitHub, GitLab, and Jira can apply a saved Markdown template or save the current description as a new one. Templates are shared between providers and stored under Neovim's data directory.


Notifications

Notifications

Open GitHub and GitLab notifications inside Atlas, refresh them, open the related item, and mark notifications as read or done without leaving Neovim.

Keep the work that needs your attention visible.


Bookmarks

Bookmarks

Turn frequently used GitHub and GitLab searches or Jira JQL into named shortcuts. Use bookmarks for review queues, recurring project views, and the searches you return to throughout the day.

Bookmarks appear alongside your configured views, keeping important queries one action away.


Custom Actions

Atlas custom action

Add project-specific actions to pull requests and issues. Custom actions receive the current item and provider context, making it possible to call local scripts, open repositories in tmux, copy branch names, or connect Atlas to your own tooling.


Configuration
pulls = {
  repo_config = {
    paths = {
      ["your-workspace/*"] = "~/code/repos/*",
    },
    settings = {},
  },
  custom_actions = {
    {
      id = "open_tmux_window",
      label = "Open repo in tmux window",
      confirmation = true,
      ---@param pr PullRequest
      ---@param ctx AtlasPullsCustomActionContext
      ---@param done fun(ok: boolean|nil, message: string|nil)
      run = function(_, ctx, done)
        if not ctx.repo_path then
          done(false, "No repo path")
          return
        end

        vim.system({ "tmux", "new-window", "-c", ctx.repo_path }, { text = true }, function(res)
          vim.schedule(function()
            if res.code ~= 0 then
              done(false, "Failed to open tmux window")
              return
            end
            done(true, "Opened tmux window")
          end)
        end)
      end,
    },
  },
},
issues = {
  custom_actions = {
    {
      id = "copy_branch_name",
      label = "Copy branch name",
      ---@param issue Issue
      ---@param ctx AtlasIssuesCustomActionContext
      ---@param done fun(ok: boolean|nil, message: string|nil)
      run = function(issue, ctx, done)
        local branch = string.format("%s/%s", issue.key, issue.summary:lower():gsub("%s+", "-"))
        vim.fn.setreg("+", branch)
        done(true, "Copied: " .. branch)
      end,
    },
  },
},

Events

Atlas emits these User events after the corresponding cleanup or setup has completed:

  • AtlasUIClosed for the main pulls/issues dashboard.
  • AtlasDiffOpened and AtlasDiffClosed for the native AtlasDiff view.
  • AtlasReviewAttached and AtlasReviewDetached for Atlas review overlays in AtlasDiff, CodeDiff, and Diffview.

Keymaps

Set an action to false to disable it, or set it to a list to add aliases.

keymaps = {
  ui = {
    next_item = "j",
    previous_item = "k",
    first_item = "gg",
    last_item = "G",
    help = "g?", -- { "g?", "<leader>?" } would add aliases
    close = "q", -- false would disable it
    toggle_panel = "p",
    toggle_fold = "za",
    toggle_all_folds = "zA",
    previous_panel_tab = "<S-Tab>",
    next_panel_tab = "<Tab>",
    open_notifications = "N",
    notifications_mark_read = "r",
    notifications_mark_done = "d",
    notifications_refresh = "R",
    toggle_subscription = "gS",
    refresh = "r",
    refresh_view = "R",
    open_actions = "A",
    open_in_browser = "gx",
    copy_id = "y",
    copy_url = "Y",
    show_details = "K",
    search = "?",
  },
  issues = {
    transition_issue = "gs",
    change_assignee = "ga",
    change_reporter = "gr",
    edit_issue = "ge",
    create_issue = "c",
  },
  pulls = {
    open_diff = "gd",
    checkout = "gc",
    review = {
      toggle_approval = "ga",
      request_changes = "gr",
      submit_review = "gs",
      open_file = "<CR>",
      toggle_explorer_grouping = "T",
      toggle_layout = "t",
      toggle_compact = "u",
      next_hunk = "]h",
      previous_hunk = "[h",
      next_file = { "]f", "<Tab>" },
      previous_file = { "[f", "<S-Tab>" },
      toggle_file_reviewed = "-",
      toggle_commits = "gC",
      toggle_review_panel = "gR",
      next_comment = "]c",
      previous_comment = "[c",
      next_note = "]n",
      previous_note = "[n",
      view_thread = "K",
      edit_comment = "e",
      add_task = "T",
      add_comment = "c",
      submit_comment = "C",
      delete_comment = "dd",
      add_note = "n",
      toggle_resolved = "x",
    },
    filter_status_open = "gpo",
    filter_status_merged = "gpm",
    filter_status_declined = "gpd",
  },
},

Contributing

Contributions are welcome! If you'd like to contribute, please open an issue or pull request on GitHub. See CONTRIBUTING.md.

Contributors ✨

Thanks go to these wonderful people (emoji key):

Riza Khan
Riza Khan

💻
Cydralic
Cydralic

💻
franroa
franroa

💻 🐛
hiddederidder
hiddederidder

💻
Xamcost
Xamcost

💻
Niklas Treml
Niklas Treml

💻 🐛
Noe Trevino
Noe Trevino

💻
Jean-Frederic Mainville
Jean-Frederic Mainville

💻

License

MIT License - see LICENSE for details.