# How to Configure Custom Autocommands in Neovim Lua: A Complete Guide

> Master Neovim Lua autocommands. Learn to create custom autocommands using vimapi nvimcreateaugroup and vimapi nvimcreateautocmd for efficient editor customization.

- Repository: [jdhao/nvim-config](https://github.com/jdhao/nvim-config)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Configure custom autocommands in Neovim Lua by using `vim.api.nvim_create_augroup` to create namespaces and `vim.api.nvim_create_autocmd` to bind Lua callbacks to editor events.**

Modern Neovim configurations leverage Lua instead of legacy VimScript for better performance and readability. The [jdhao/nvim-config](https://github.com/jdhao/nvim-config) repository demonstrates a clean, maintainable pattern for organizing custom autocommands that automate workflows, enhance UI behavior, and optimize file handling.

## Core API for Defining Autocommands

Neovim exposes autocommand functionality through the `vim.api` module. Every custom automation relies on two functions: **`nvim_create_augroup`** establishes a container for related commands, and **`nvim_create_autocmd`** registers the actual event listener.

In [`lua/custom-autocmd.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/custom-autocmd.lua), the author imports the API locally for brevity:

```lua
local api = vim.api

-- Create a namespace for related autocommands
local my_group = api.nvim_create_augroup("MyCustomGroup", { clear = true })

-- Register an event handler
api.nvim_create_autocmd({ "BufWritePre" }, {
    pattern = "*",
    group = my_group,
    callback = function(ev)
        -- Logic executes before saving any file
    end,
})

```

Setting `clear = true` when creating the group ensures that reloading your configuration does not duplicate autocommands.

## Setting Up Your Autocommand Module

The jdhao/nvim-config repository centralizes all custom autocommands in **[`lua/custom-autocmd.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/custom-autocmd.lua)**, keeping the entry point uncluttered. This module is loaded from **[`init.lua`](https://github.com/jdhao/nvim-config/blob/main/init.lua)** at line 24:

```lua
-- In init.lua
require("custom-autocmd")

```

This modular approach allows you to add, remove, or debug event handlers without touching plugin specifications or key mappings. Helper utilities reside in [`lua/utils.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/utils.lua), providing shared functions for filesystem checks and executable detection that multiple autocommands can reference.

## Practical Autocommands for Editing Workflows

### Highlight Yanked Text with TextYankPost

Visual feedback improves editing confidence. Lines 20–27 of [`lua/custom-autocmd.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/custom-autocmd.lua) implement temporary highlighting using the `TextYankPost` event, which fires after text is yanked:

```lua
local yank_grp = api.nvim_create_augroup("highlight_yank", { clear = true })

api.nvim_create_autocmd({ "TextYankPost" }, {
    pattern = "*",
    group = yank_grp,
    callback = function()
        vim.hl.on_yank { higroup = "YankColor", timeout = 300 }
    end,
})

```

The repository also preserves cursor position across yanks by storing coordinates on `CursorMoved` and restoring them after `TextYankPost` (lines 29–46).

### Automate Directory Creation on Save

Never encounter "directory does not exist" errors again. The `BufWritePre` event triggers before writing a buffer to disk, allowing the configuration to create missing parent directories automatically (lines 48–55):

```lua
api.nvim_create_autocmd({ "BufWritePre" }, {
    pattern = "*",
    group = api.nvim_create_augroup("auto_create_dir", { clear = true }),
    callback = function(ev)
        local dir = vim.fn.fnamemodify(ev.file, ":p:h")
        if vim.fn.isdirectory(dir) == 0 then
            vim.fn.mkdir(dir, "p")
        end
    end,
})

```

Here, `ev.file` contains the target path, and `vim.fn.fnamemodify` extracts the directory component before `vim.fn.mkdir` creates it recursively.

### Reload Files Changed on Disk

When external tools modify files (e.g., git checkout), Neovim can automatically reload buffers. Lines 58–82 implement a smart check that runs `checktime` only when not actively typing in the command-line window:

```lua
api.nvim_create_augroup("auto_read", { clear = true })

api.nvim_create_autocmd({ "FocusGained", "CursorHold" }, {
    pattern = "*",
    group = "auto_read",
    callback = function()
        if vim.fn.getcmdwintype() == "" then
            vim.cmd("checktime")
        end
    end,
})

```

The `getcmdwintype()` check prevents disruptive prompts while entering Ex commands.

## Window and UI Management Autocommands

### Resize Splits Automatically

Maintain split proportions when the terminal window changes size using `VimResized` (lines 85–90):

```lua
api.nvim_create_autocmd({ "VimResized" }, {
    pattern = "*",
    group = api.nvim_create_augroup("win_resize", { clear = true }),
    command = "wincmd =",
})

```

This executes the Ex command `wincmd =` to equalize split dimensions.

### Dynamic Line Number Behavior

Toggle between relative and absolute line numbers based on buffer focus and edit mode. Lines 143–163 define a group that enables relative numbers on `BufEnter` and `FocusGained`, but disables them on `InsertEnter`:

```lua
local num_grp = api.nvim_create_augroup("numbertoggle", { clear = true })

api.nvim_create_autocmd({ "BufEnter", "FocusGained", "InsertLeave" }, {
    pattern = "*",
    group = num_grp,
    callback = function()
        vim.opt.relativenumber = true
    end,
})

api.nvim_create_autocmd({ "BufLeave", "FocusLost", "InsertEnter" }, {
    pattern = "*",
    group = num_grp,
    callback = function()
        vim.opt.relativenumber = false
    end,
})

```

## Terminal and Command-Line Customizations

### Configure Terminal Buffers

Terminal buffers require distinct settings. The `TermOpen` event (lines 130–141) disables line numbers and starts insert mode automatically:

```lua
api.nvim_create_autocmd({ "TermOpen" }, {
    pattern = "*",
    group = api.nvim_create_augroup("term_settings", { clear = true }),
    callback = function()
        vim.opt.number = false
        vim.opt.relativenumber = false
        vim.cmd("startinsert")
    end,
})

```

### Disable Smartcase in Command Mode

Prevent case-insensitive search from interfering while typing commands. Lines 112–128 toggle the `smartcase` option when entering and leaving the command line:

```lua
api.nvim_create_autocmd({ "CmdLineEnter" }, {
    pattern = "*",
    group = api.nvim_create_augroup("cmdline_config", { clear = true }),
    callback = function()
        vim.opt.smartcase = false
    end,
})

api.nvim_create_autocmd({ "CmdLineLeave" }, {
    pattern = "*",
    group = "cmdline_config",
    callback = function()
        vim.opt.smartcase = true
    end,
})

```

## Performance Optimizations

### Handle Large Files Efficiently

Editing files larger than 0.5 MiB can slow Neovim significantly. Lines 332–356 detect large files on `BufReadPre` and disable expensive features:

```lua
api.nvim_create_autocmd({ "BufReadPre" }, {
    pattern = "*",
    group = api.nvim_create_augroup("large_file", { clear = true }),
    callback = function(ev)
        local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(ev.buf))
        if ok and stats and stats.size > 524288 then
            vim.opt.eventignore:append("FileType")
            vim.opt.number = false
            vim.opt.swapfile = false
            -- Additional optimizations...
        end
    end,
})

```

This uses `vim.loop.fs_stat` to check file size before applying performance tweaks.

### Auto-Format on Save with Error Handling

Lines 358–392 demonstrate running external formatters in check mode after `BufWritePost`, parsing output and notifying only on failure. This pattern uses a filetype map to determine which executable to invoke, keeping the configuration modular.

## Summary

- **Use augroups**: Always create a dedicated `augroup` with `clear = true` to organize related autocommands and prevent duplication on config reload.
- **Prefer Lua callbacks**: Write `callback = function() ... end` instead of raw `command` strings for better performance and access to `vim.*` APIs.
- **Centralize configuration**: Follow the jdhao/nvim-config pattern by placing all autocommands in [`lua/custom-autocmd.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/custom-autocmd.lua) and requiring it from [`init.lua`](https://github.com/jdhao/nvim-config/blob/main/init.lua) (line 24).
- **Leverage event data**: Use the `ev` parameter in callbacks to access buffer numbers, file paths, and other context without global state.
- **Optimize conditionally**: Check `vim.fn` states (like `getcmdwintype()`) or file stats before executing heavy operations.

## Frequently Asked Questions

### How do I prevent duplicate autocommands when reloading my config?

Pass `{ clear = true }` as the second argument to `vim.api.nvim_create_augroup`. This clears existing autocommands in that group before adding new ones, ensuring clean state after `:source` or config reloads.

### What is the difference between `callback` and `command` in `nvim_create_autocmd`?

`callback` accepts a Lua function that executes in a fresh context with access to the `ev` table containing event details, while `command` accepts a raw Vim Ex command string. Use `callback` for complex logic and `command` for simple one-liners like `"wincmd ="`.

### How can I make autocommands trigger only for specific filetypes?

Set the `pattern` option to a filetype pattern such as `*.lua` or use the `group` parameter with conditional logic inside the callback checking `vim.bo.filetype`. For multiple specific filetypes, use an array: `pattern = { "*.py", "*.lua" }`.

### Where should I place utility functions used by multiple autocommands?

Create a separate module like [`lua/utils.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/utils.lua) to host shared helpers such as `may_create_dir` or `inside_git_repo`, then require it at the top of [`lua/custom-autocmd.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/custom-autocmd.lua). This keeps your autocommand definitions readable and your logic reusable across the configuration.