Commands and Keymaps
Add user-facing actions.
Commands and Keymaps is a free Lua Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
User Commands Overview
User commands are colon-commands your plugin exposes, like :Format or :Telescope. They must start with an uppercase letter.
nvim_create_user_command defines them in Lua, taking a name, a callback or string, and an options table. This replaces Vimscript's :command.
vim.api.nvim_create_user_command('Hello', function()
print('Hello from my plugin')
end, {})Command Arguments
Set nargs to accept arguments: '0', '1', '*', '?', or '+'. The callback receives an opts table with args, fargs, and bang.
fargs is the argument list already split on whitespace, which is usually what you want.
vim.api.nvim_create_user_command('Greet', function(opts)
print('Hi ' .. opts.args)
end, { nargs = 1 })Range and Bang Commands
Pass range = true to accept line ranges; the callback then reads opts.line1 and opts.line2. Add bang = true to allow :Cmd!, detected via opts.bang.
These flags let one command behave differently for visual selections or forced variants.
vim.api.nvim_create_user_command('Sum', function(o)
print(o.line1 .. ' to ' .. o.line2)
end, { range = true })Command Completion
Provide tab-completion with the complete option. Use built-ins like 'file' or 'buffer', or a custom Lua function.
A custom completer receives the partial argument and the command line, returning a list of candidate strings.
vim.api.nvim_create_user_command('Pick', function(o) print(o.args) end, {
nargs = 1,
complete = function() return { 'red', 'green', 'blue' } end,
})Buffer-Local Commands
To scope a command to one buffer, use nvim_buf_create_user_command with the buffer handle. This is common in filetype plugins.
For example, a Markdown plugin might define :Preview only inside Markdown buffers, keeping the global namespace clean.
vim.api.nvim_buf_create_user_command(0, 'Preview', function()
print('previewing this buffer')
end, {})Introducing vim.keymap.set
vim.keymap.set is the modern mapping API. It takes a mode, the left-hand keys, a right-hand string or Lua function, and an options table.
Unlike the old nvim_set_keymap, it accepts a Lua callback directly as the right-hand side, no <cmd>lua wrapping needed.
vim.keymap.set('n', '<leader>w', function()
vim.cmd('write')
end, { desc = 'Save file' })Modes and Multiple Mappings
The mode argument can be a single string like 'n' or a table such as { 'n', 'v' } to map several modes at once.
Common modes: n normal, i insert, v visual, x visual-only, t terminal. An empty string '' means normal, visual, and operator-pending.
vim.keymap.set({ 'n', 'v' }, '<leader>y', '"+y', { desc = 'Yank to clipboard' })Mapping Options
The opts table controls behavior. silent = true suppresses the command echo, noremap is true by default for safety, and buffer = 0 scopes the map to the current buffer.
Always add a desc so which-key and :map output stay readable.
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, {
buffer = 0, silent = true, desc = 'Go to definition',
})Expression Mappings
With expr = true, the right-hand side function returns the keys to feed. This powers smart mappings, like making <Tab> behave differently when a completion menu is visible.
Return an empty string to do nothing, or the literal keys to insert.
vim.keymap.set('i', '<Tab>', function()
return vim.fn.pumvisible() == 1 and '<C-n>' or '<Tab>'
end, { expr = true })Deleting Mappings
Remove a mapping with vim.keymap.del, passing the same mode and keys, plus a buffer field if it was buffer-local.
This is useful in cleanup routines or when a plugin toggles a feature off and must restore the user's original bindings.
vim.keymap.del('n', '<leader>w')
vim.keymap.del('n', 'gd', { buffer = 0 })Commands Plus Keymaps Together
A clean pattern is to define the logic once, expose it as a user command, and bind a key that calls that command. This keeps a single source of truth.
Mapping to <cmd>Hello<cr> avoids leaving visual mode and is more predictable than :Hello<cr>.
vim.api.nvim_create_user_command('Toggle', function() end, {})
vim.keymap.set('n', '<leader>t', '<cmd>Toggle<cr>', { desc = 'Toggle' })Quick Check
Check your grasp of commands and keymaps.
Recap: Commands and Keymaps
You can now create commands with nvim_create_user_command, control them via nargs, range, bang, and complete, and scope them per buffer.
For mappings, vim.keymap.set accepts Lua callbacks, multiple modes, and options like desc, silent, buffer, and expr, with vim.keymap.del for cleanup.
Frequently asked questions
Is the “Commands and Keymaps” lesson free?
Yes — the full text of “Commands and Keymaps” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.
What will I learn in “Commands and Keymaps”?
Add user-facing actions. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Lua Academy?
No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Commands and Keymaps” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Lua Academy lesson?
Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- The Neovim Lua API
- Commands and Keymaps
- Buffers and Windows
- Packaging a Plugin