How to Manage Snippets with UltiSnips in Neovim: A Complete Configuration Guide

UltiSnips in jdhao/nvim-config expands snippets with Ctrl‑J, jumps forward with the same key, jumps backward with Ctrl‑K, and scans both the bundled UltiSnips collection and a custom my_snippets/ directory for user definitions.

The jdhao/nvim-config repository provides a battle-tested setup for managing code snippets in Neovim using UltiSnips as the snippet engine. This configuration uses lazy.nvim for deferred loading and defines sensible global variables that control expansion triggers and search paths.

UltiSnips Plugin Configuration and Lazy Loading

The plugin specification lives in lua/plugin_specs.lua at lines 81‑99. UltiSnips is loaded lazily via the event = "InsertEnter" trigger, ensuring the plugin only activates when you actually need to insert text.

Inside the plugin spec, an init function executes vim.cmd to set global variables before the plugin loads:

init = function()
  vim.cmd([[
    let g:UltiSnipsExpandTrigger='<c-j>'
    let g:UltiSnipsJumpForwardTrigger='<c-j>'
    let g:UltiSnipsJumpBackwardTrigger='<c-k>'
    let g:UltiSnipsEnableSnipMate=0
    let g:UltiSnipsSnippetDirectories=['UltiSnips', 'my_snippets']
  ]])
end

These settings establish three critical behaviors:

  • <c-j> (Ctrl‑J) serves dual duty: it expands the snippet under the cursor and subsequently jumps to the next tabstop.
  • <c-k> (Ctrl‑K) moves the cursor to the previous tabstop when you need to backtrack.
  • SnipMate compatibility is disabled (g:UltiSnipsEnableSnipMate=0) to prevent conflicts with legacy snippet formats.

Snippet Directory Structure and Search Paths

The g:UltiSnipsSnippetDirectories variable tells UltiSnips exactly where to look for snippet definitions. The configuration specifies two locations:

  1. UltiSnips — The bundled collection that ships with the plugin.
  2. my_snippets — A user-maintained directory at the repository root for personal snippets.

The repository includes several example files under my_snippets/:

  • my_snippets/python.snippets — Python-specific triggers.
  • my_snippets/markdown.snippets — Markdown formatting shortcuts.
  • my_snippets/cpp.snippets — C++ boilerplate snippets.

You can add new files following the pattern {filetype}.snippets to target specific languages.

Creating Custom Snippets with Proper Filetype Detection

To make editing snippet files easier, the configuration includes a filetype detector at ftdetect/snippets.vim. This autocommand recognizes *.snippets files and sets the snippets filetype, enabling syntax highlighting and filetype-specific commands when you edit your definitions.

To create a custom snippet, add a definition to my_snippets/{language}.snippets using UltiSnips syntax:

snippet def "Python function definition"
def ${1:function_name}(${2:args}):
    ${0:# body}

endsnippet

This defines a trigger word def with a description. Placeholders follow the ${number:default} syntax, where ${0} marks the final cursor position.

Daily Workflow: Expanding and Navigating Snippets

Once you enter Insert mode, lazy.nvim sources UltiSnips automatically. The engine scans the configured directories and indexes all available snippets for the current buffer’s filetype.

To use a snippet:

  1. Type the trigger word (e.g., def).
  2. Press Ctrl‑J to expand the template.
  3. The cursor lands on the first placeholder (${1}).
  4. Press Ctrl‑J again to jump to ${2}, or Ctrl‑K to move back to the previous field.

The mirrored trigger for expansion and forward navigation reduces cognitive load—you only need to remember Ctrl‑J to advance through the snippet.

Summary

  • Configuration location: lua/plugin_specs.lua lines 81‑99 defines UltiSnips settings via an init function that sets global variables before the plugin loads.
  • Key mappings: Ctrl‑J expands snippets and jumps forward; Ctrl‑K jumps backward; SnipMate is explicitly disabled to avoid conflicts.
  • Search paths: UltiSnips looks in both the bundled UltiSnips directory and the user-managed my_snippets/ folder.
  • Filetype support: ftdetect/snippets.vim ensures *.snippets files receive proper syntax highlighting.
  • Creation workflow: Add {filetype}.snippets files to my_snippets/, reload, and expand triggers with Ctrl‑J.

Frequently Asked Questions

How do I change the UltiSnips expansion trigger from Ctrl‑J to Tab?

Edit the plugin specification in lua/plugin_specs.lua and modify the g:UltiSnipsExpandTrigger variable. Change '<c-j>' to '<tab>' inside the init function’s vim.cmd block, then restart Neovim.

Where should I place my personal snippet files in this configuration?

Store all custom snippets in the my_snippets/ directory at the repository root. Create files named after the target filetype (e.g., rust.snippets, javascript.snippets) so UltiSnips automatically associates them with the correct buffers.

Why does UltiSnips use the same key for expanding and jumping forward?

The configuration intentionally sets g:UltiSnipsExpandTrigger and g:UltiSnipsJumpForwardTrigger both to '<c-j>'. This design creates a consistent workflow: pressing Ctrl‑J always moves you to the next logical step, whether that is expanding a new snippet or advancing to the next placeholder in an active one.

How do I disable the bundled UltiSnips snippets and use only my own?

Remove 'UltiSnips' from the g:UltiSnipsSnippetDirectories list in lua/plugin_specs.lua. Set the variable to only ['my_snippets'] so UltiSnips exclusively searches your custom directory and ignores the default collection.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →