Open-source project
windwp/nvim-autopairs avatar
windwp/nvim-autopairs

nvim-autopairs: rule-based bracket pairing for Neovim

autopairs for neovim written in lua

4,100 stars141 forksLuaMIT

At a glance

What is it?
nvim-autopairs pairs brackets, quotes and custom tokens in insert mode using a rule engine with conditions. It is small, MIT licensed, and depends on Neovim 0.7 or newer.
Who is it for?
Adopt it if you run Neovim 0.7 or newer and want pairing behaviour you can shape per filetype through Rule objects and conditions. Skip it if you want pairing decisions made from a syntax tree out of the box, since check_ts defaults to false.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 40 days ago.
What is it written in?
Mainly Lua, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What nvim-autopairs does that a plain mapping does not

A naive autopair mapping inserts a closing character whenever you type an opening one. That breaks the moment the next character is already a closing bracket, when you are inside a string, or when you are recording a macro. nvim-autopairs exists to make those decisions conditional. The README describes it as an autopair plugin that supports multiple characters, and the rule system is the reason the description holds up: each pair is a Rule object, and each Rule can carry a set of conditions that decide whether to add the pair, whether to move the cursor right over an existing closing character, whether to delete, and whether pressing Enter should expand the pair into two lines.

The audience is Neovim users who write in more than one language and have been annoyed by a single hardcoded pair table. TeX users who want $$ to expand, Lua users who want a regex-triggered rule, and people who want pairing disabled in a specific picker buffer all configure the same mechanism. The plugin requires Neovim 0.7, which is the floor stated in the README.

Rules, conditions and the insert-mode data flow

The mechanism is a list of rules evaluated against the current line and cursor position. The README shows the shape directly: Rule("$$", "$$", "tex") creates a rule whose opening and closing tokens are both $$ and whose filetype is tex. The third argument can also be a negated filetype, as in Rule("a", "a", "-vim"), which the README says disables the rule for .vim files while leaving it active elsewhere.

Conditions come from the nvim-autopairs.conds module, and the README suggests printing it to see what is available. The example chains with_pair(cond.not_after_regex("%%")), with_pair(cond.not_before_regex("xxx", 3)), with_move(cond.none()), with_del(cond.not_after_regex("xx")) and with_cr(cond.none()). Each method narrows one behaviour of the rule rather than the rule as a whole, so a pair can be inserted but not auto-expanded on Enter. A condition can also be a plain function that receives an opts table; the README prints that table and returns false when the line matches a specific string.

Regex rules go through use_regex(true). The README gives Rule("u%d%d%d%d$", "number", "lua") with use_regex(true), which turns typing u1234 into u1234number, and a second variant using replace_endpair so that x1234 becomes x12341234. This is the part that separates the plugin from a fixed bracket table: the trigger is a pattern, and the inserted text is computed.

Defaults matter here. disable_in_macro is true, so pairing does not fire while a macro is being recorded or executed. disable_in_replace_mode is true. enable_moveright is true, which is what lets a typed closing bracket skip over the one already present. break_undo is true, described as a switch for the basic rule to break the undo sequence, meaning an inserted pair does not merge with the surrounding edit in the undo history.

Installing nvim-autopairs with lazy.nvim, vim-plug or packer

The README lists three package managers. With lazy.nvim the plugin is loaded on the InsertEnter event and configured with config = true, which the README says is equivalent to calling setup({}). The comment in the snippet notes that opts = {} is the way to pass setup options instead.

lua
{
    'windwp/nvim-autopairs',
    event = "InsertEnter",
    config = true
    -- use opts = {} for passing setup options
    -- this is equivalent to setup({}) function
}

With vim-plug the plugin is declared and then configured inside a lua heredoc.

vim
Plug 'windwp/nvim-autopairs'

lua << EOF
require("nvim-autopairs").setup {}
EOF

Packer uses the same InsertEnter event with an explicit config function that calls setup with an empty table. After installation, the first real use is overriding a default. The README shows replacing the disable_filetype list, which by default contains TelescopePrompt, spectre_panel and snacks_picker_input.

lua
require('nvim-autopairs').setup({
  disable_filetype = { "TelescopePrompt" , "vim" },
})

With that in place, typing an opening bracket in a normal buffer should insert the closing character and leave the cursor between them. If it does not, the README points at two suspects: the plugin not being loaded for the buffer, or an indent setting from treesitter fighting the newline behaviour.

Where completion plugins change the picture

Pairing and completion interact badly if you ignore the interaction. When a completion menu is open, Enter has two possible meanings: accept the item, or expand the pair. The README addresses this for nvim-cmp by requiring a CR mapping in the cmp setup and then registering a confirm_done handler.

lua
local cmp_autopairs = require('nvim-autopairs.completion.cmp')
local cmp = require('cmp')
cmp.event:on(
  'confirm_done',
  cmp_autopairs.on_confirm_done()
)

The handler can be scoped per filetype and per character. The README shows a table keyed by filetype where "*" acts as a fallback for all filetypes, and a per-character entry that names the completion kinds which should trigger an opening parenthesis. The kinds come from cmp.lsp.CompletionItemKind, and the example lists Function and Method. A filetype can be set to false to disable the behaviour, and the README warns explicitly against using nil for that purpose, because nil falls back to "*".

For coq_nvim the README takes a different route: it disables the plugin's own mappings with map_bs = false and map_cr = false, then rebuilds them around pumvisible() checks and the exported npairs.autopairs_cr() and npairs.autopairs_bs() functions. That is a heavier setup, and it is the honest signal that the plugin's default CR handling assumes it owns the key. If you use a completion plugin the README does not cover, the wiki page linked as "another completion plugin" is where the project points.

Limitations and the cases where this is the wrong tool

The clearest limitation is in the defaults table. check_ts is false. Pairing decisions are not derived from the syntax tree unless you turn that on, so the plugin is reasoning about the line text, regexes and conditions you write, not about whether the cursor is actually inside a string or a comment. For most editing that is fine. In a file where brackets inside strings and brackets in code look identical to a line-based check, it is not, and you will be writing conditions to compensate.

The README also points elsewhere for indentation. If the result after pressing Enter is badly indented, the advice is to check the treesitter indent settings or install a plugin with indent support for that filetype. That is an admission of a boundary: this plugin decides whether a pair exists, not how the resulting lines are indented.

The completion integration is where setup cost concentrates. The nvim-cmp snippet is short, but the coq_nvim snippet replaces several insert-mode mappings and defines a global MUtils table to route CR and BS through pumvisible() checks. If you are not prepared to own that mapping logic, pairing and completion will fight over Enter.

Finally, disable_in_macro is on by default. If your workflow depends on macros that insert brackets, the pairs will not appear during recording or execution, and that is a deliberate choice rather than a bug.

How it differs from mini.pairs and nvim-surround

mini.pairs is the closest comparison, and the difference is scope. This plugin ships a rule engine with a conditions module, regex triggers and replace_endpair, which is what lets you express things like u1234 becoming u1234number. A minimal pairing module does not carry that layer: you get sensible pairing behaviour and little machinery for inventing new triggers. If you never write a custom Rule, you are paying for a rule engine you do not use, and the smaller module is the better fit.

nvim-surround solves a different problem. It is about adding, changing and deleting surrounding characters around existing text, which is an editing operation you invoke, not an insert-mode reaction to a keystroke. The two are complementary rather than competing, and the fact that both appear in search results for this plugin suggests people conflate them. If your complaint is "I want to wrap this word in quotes", nvim-surround is the tool. If your complaint is "the closing bracket should not appear when the next character is already one", that is this plugin.

The plugin also has no dependency on a completion framework. nvim-cmp integration is opt-in through a separate module, and the README documents a path for running without any completion plugin at all by setting map_cr = true.

Maintenance, licence and what upgrading costs

The repository is not archived, and the last push was on 2026-08-23. The most recent release listed is 0.10.0, tagged 0.11.0, dated 2025-09-26. That mismatch between the release name and the tag is worth noting if you pin versions in a lockfile, because the version string you write may not match the tag you expect.

The licence is MIT, which permits use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive arrangement, and it is compatible with the usual ways people distribute Neovim configurations. This is a description of the licence text, not legal advice; if you redistribute the plugin inside a product, read the LICENSE file in the repository root yourself.

The project has a Makefile with three targets, which tells you something about the contribution and upgrade surface. Running make test executes the PlenaryBustedDirectory suite headlessly against tests/minimal.vim. The test-file target runs a single file through plenary.busted. The fmt target runs stylua over the tree, and the repository carries .stylua.toml and .styluaignore alongside .luarc.json and .editorconfig. If you fork the plugin, those files define the expected formatting and language-server setup.

Upgrade cost is low in normal use because the public surface is setup(), add_rule, add_rules and the completion handlers. The risk sits in the completion table, where filetype keys and completion kinds are matched exactly and a nil value silently falls back to "*".

Editorial conclusion

Adopt it if you run Neovim 0.7 or newer and want pairing behaviour you can shape per filetype through Rule objects and conditions. Skip it if you want pairing decisions made from a syntax tree out of the box, since check_ts defaults to false. Before committing, read the default values table and decide what to override: disable_filetype already excludes TelescopePrompt, and map_c_h and map_c_w are off by default. If you use nvim-cmp, wire the confirm_done event before judging whether the plugin feels right.

Frequently asked questions

What is nvim-autopairs and what does it do?

It is an autopair plugin for Neovim written in Lua, described in the README as supporting multiple characters. It inserts closing characters as you type and uses rules with conditions to decide when to add, move over, delete or expand a pair.

What does nvim-autopairs do when you type a bracket?

It inserts the matching closing character and leaves the cursor between the two, subject to the conditions attached to that rule. With enable_moveright set to its default of true, typing a closing character that is already present moves the cursor past it instead of inserting a duplicate.

How do I configure nvim-autopairs?

Call require('nvim-autopairs').setup({}) with a table of overrides. The README's example replaces disable_filetype, which by default contains TelescopePrompt, spectre_panel and snacks_picker_input.

How does nvim-autopairs work with nvim-cmp?

You register a confirm_done handler with cmp.event:on and require('nvim-autopairs.completion.cmp'), and you add a CR mapping in the cmp setup. The handler can be scoped per filetype and per completion kind, with "*" acting as the fallback for all filetypes.

Why is nvim-autopairs not working in some buffers?

Check disable_filetype first, since TelescopePrompt, spectre_panel and snacks_picker_input are excluded by default. Pairing is also suppressed while a macro is being recorded or executed because disable_in_macro defaults to true, and in replace mode because disable_in_replace_mode defaults to true.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. windwp/nvim-autopairs on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/windwp-nvim-autopairs.svg)](https://hysenlabs.com/projects/windwp-nvim-autopairs)