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

> Master UltiSnips in Neovim with this comprehensive configuration guide. Learn to expand snippets, jump forward/backward, and manage custom snippets efficiently through CtrlJ and CtrlK.

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

---

**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`](https://github.com/jdhao/nvim-config/blob/main/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:

```lua
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:

```text
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`](https://github.com/jdhao/nvim-config/blob/main/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`](https://github.com/jdhao/nvim-config/blob/main/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`](https://github.com/jdhao/nvim-config/blob/main/lua/plugin_specs.lua). Set the variable to only `['my_snippets']` so UltiSnips exclusively searches your custom directory and ignores the default collection.