Snippets let you insert reusable text templates with interactive fields. Type a trigger prefix and press Tab to expand — cursor jumps between editable placeholders. The system supports the VS Code/LSP snippet format, ships with 60+ Obsidian-adapted templates, and can be extended with user-defined JSON files or a Lua DSL.

Trigger mechanisms

Snippets can be triggered three ways, controlled by the Trigger mode setting:

TriggerHow it works
Tab expansionType the snippet prefix in insert mode, press Tab. The prefix is replaced with the expanded template.
Completion menuAs you type, matching snippets appear in the CM6 autocomplete popup. Select one to expand.
Ex command:snippet <name> expands by snippet name. :snippets opens the picker.

The picker (:snippets) provides fuzzy search across all loaded snippets with a preview pane showing the expanded body.

Tabstop navigation

After expansion, the cursor lands on the first tabstop. Navigate between fields:

KeyAction
TabMove to next tabstop
Shift+TabMove to previous tabstop
EscapeExit snippet mode

Linked mirrors: When the same tabstop number appears multiple times in a template, editing one updates all instances simultaneously.

Bundled snippets

The plugin ships with Obsidian-specific snippets (enabled by default, toggle in settings):

Markdown formatting

PrefixNameOutput
h1h6Headings# Heading through ###### Heading
boldBold**text**
italicItalic*text*
biBold italic***text***
strikeStrikethrough~~text~~
hlHighlight==text==
icInline code`code`
cbCode blockFenced code block with language tabstop
linkLink[text](url)
imgImage![alt](url)
quoteBlockquote> text
hrHorizontal rule---

Lists and tasks

PrefixNameOutput
olOrdered listThree numbered items
ulUnordered listThree bullet items
taskTask- [ ] task

Obsidian-specific

PrefixNameOutput
wlWikilink[[page]]
wlaWikilink with alias[[page|alias]]
embedEmbed![[file]]
commentComment%%comment%%
fmFrontmatterYAML frontmatter with title, date, tags
mathMath blockDisplay math $$..$$
imInline mathInline math $...$
tableTable 2×2Two-column table with header
table3Table 3×3Three-column table with header

Callouts

All Obsidian callout types are available with a c prefix:

cnote, cabstract, csummary, ctldr, cinfo, ctodo, ctip, chint, cimportant, csuccess, ccheck, cdone, cquestion, chelp, cfaq, cwarning, ccaution, cattention, cfailure, cfail, cmissing, cdanger, cerror, cbug, cexample, cquote, ccite

Each expands to > [!type] Title with tabstops on the title and content.

Date and utility

PrefixNameOutput
dateDate (ISO)2026-07-13
datelDate (long)July 13, 2026
timeTime14:30:00
datetimeDate and time2026-07-13 14:30:00
disoISO 86012026-07-13T14:30:00
uuidUUID v4Random UUID

User-defined snippets

Point the Snippet directory setting to a folder containing .json files in VS Code snippet format:

{
    "My Snippet": {
        "prefix": "mysnip",
        "body": ["Hello ${1:World}!", "$0"],
        "description": "A greeting snippet"
    }
}

User snippets override bundled snippets when they share the same prefix — the bundled entry is removed from the completion menu, picker, and Tab expansion. The override priority order is user > lua > bundled: user JSON snippets always win, Lua snippets override bundled but yield to user JSON, and bundled snippets are defaults that yield to any explicit definition. When a multi-prefix bundled snippet is partially overridden (e.g., user overrides prefix a but not prefix b), the bundled snippet remains accessible via the non-overridden prefix. The directory supports absolute paths (with ~ expansion) and vault-relative paths.

Snippet body syntax

SyntaxDescription
$1, $2Tabstops (navigate with Tab/Shift+Tab)
$0Final cursor position
${1:default}Tabstop with default text
${1|a,b,c|}Choice node (cycle with Ctrl+N/Ctrl+P)
${1:${2:nested}}Nested placeholders
$CURRENT_YEARVariable (resolved at expansion)

Supported variables

Selection and content

VariableValue
$TM_SELECTED_TEXTCurrently selected text
$VISUALAlias for $TM_SELECTED_TEXT (vim convention)
$TM_CURRENT_LINEContents of the current line
$TM_CURRENT_WORDWord under cursor
$WORDAlias for $TM_CURRENT_WORD (vim convention)
$TM_LINE_NUMBERLine number (1-based)
$TM_LINE_INDEXLine number (0-based)
$CLIPBOARDClipboard contents

File and path

VariableValue
$TM_FILENAMECurrent filename with extension
$TM_FILENAME_BASECurrent filename without extension
$TM_FILEPATHFull vault path
$TM_DIRECTORYParent directory path
$RELATIVE_FILEPATHVault-relative path (same as $TM_FILEPATH in Obsidian)

Workspace and cursor

VariableValue
$WORKSPACE_NAMEVault name
$WORKSPACE_FOLDERVault name (absolute path not available in Obsidian)
$CURSOR_INDEXCursor index (0-based, always 0)
$CURSOR_NUMBERCursor number (1-based, always 1)

Date and time

VariableValue
$CURRENT_YEARFull year (e.g. 2026)
$CURRENT_YEAR_SHORTLast 2 digits of year
$CURRENT_MONTHMonth 01–12
$CURRENT_MONTH_NAMEMonth name (e.g. July)
$CURRENT_MONTH_NAME_SHORTMonth name abbreviation (e.g. Jul)
$CURRENT_DATEDay 01–31
$CURRENT_DAY_NAMEWeekday name (e.g. Monday)
$CURRENT_DAY_NAME_SHORTWeekday abbreviation (e.g. Mon)
$CURRENT_HOURHour 00–23
$CURRENT_MINUTEMinute 00–59
$CURRENT_SECONDSecond 00–59
$CURRENT_MILLISECONDMillisecond 000–999
$CURRENT_SECONDS_UNIXUnix timestamp (seconds)
$CURRENT_MILLISECONDS_UNIXUnix timestamp (milliseconds)
$CURRENT_TIMEZONE_OFFSETUTC offset (e.g. +0200)
$CURRENT_TIMEZONE_NAMETimezone name (e.g. Central European Summer Time)

Random

VariableValue
$RANDOM6-digit random number
$RANDOM_HEX6-digit random hex
$UUIDRandom UUID v4

$TM_SELECTED_TEXT / $VISUAL resolve to the visual selection when expanding via the :snippet command. Select text in visual mode, then type :snippet <name> to wrap or transform the selection. In tab-expand mode, selection is not available (tab expansion requires an empty selection). On mobile, $CLIPBOARD may resolve to empty due to browser clipboard API restrictions.

Context filtering

Snippets can be restricted to specific editing contexts with the "context" field:

{
    "JS Function": {
        "prefix": "fn",
        "body": ["function ${1:name}(${2:args}) {", "\t$0", "}"],
        "context": "code:js"
    }
}
Context valueActive when cursor is in
"prose"Normal markdown text (not in code block or frontmatter)
"code:*"Any fenced code block
"code:js"A ```js code block (language-specific)
"frontmatter"YAML frontmatter block
(omitted)Available everywhere

Lua snippet DSL

Define snippets in .obsidian.init.lua using a LuaSnip-inspired DSL:

local s = vim.snippet.s
local t = vim.snippet.t
local i = vim.snippet.i
local c = vim.snippet.c
local fmt = vim.snippet.fmt
 
-- Simple snippet
vim.snippet.add("greet", s("Greeting", {
    t("Hello, "), i(1, "world"), t("!")
}))
 
-- Using fmt (most common pattern)
vim.snippet.add("fn", s("Function", fmt([[
function {}({})
    {}
end
]], { i(1, "name"), i(2, "args"), i(0) })))
 
-- With choices
vim.snippet.add("log", s("Console Log", {
    t("console."), c(1, { t("log"), t("warn"), t("error") }),
    t("("), i(2, "msg"), t(")"),
}))
 
-- With context
vim.snippet.add("ret", s("Return", {
    t("return "), i(1),
}, { context = "code:*" }))

DSL functions

FunctionDescription
vim.snippet.s(name, nodes, opts?)Define a snippet
vim.snippet.t(text)Static text node
vim.snippet.i(index, default?)Editable tabstop
vim.snippet.c(index, choices)Choice node (list of t() nodes)
vim.snippet.rep(index)Mirror/repeat a tabstop
vim.snippet.fmt(str, nodes, opts?)Format string with {} placeholders
vim.snippet.f(fn, deps)Function node — computes text from dependency field values
vim.snippet.d(index, fn, deps)Dynamic node — generates sub-snippet based on field values
vim.snippet.sn(index, nodes, opts?)Snippet node — wraps nodes for use as d() return value
vim.snippet.r(index, type_name?)Restore node — preserves user input across d() regeneration
vim.snippet.add(trigger, snippet)Register a snippet
vim.snippet.add_all(table)Register multiple snippets

Static snippets (t, i, c, rep, fmt) compile to VS Code JSON at load time. Dynamic snippets (f, d, r) execute Lua functions reactively during snippet editing.

Dynamic snippets

Dynamic snippets update in real time as you edit fields.

f() — function node: Computes text from other field values. Updates automatically when dependency fields change.

vim.snippet.add("mirror", s("Mirror", {
    i(1, "hello"),
    t(" → "),
    f(function(args) return string.upper(args[1]) end, { 1 }),
}))
-- Typing "world" in field 1 → output: "world → WORLD"

d() — dynamic node: Generates an entire sub-snippet based on field values. Regenerates when dependencies change.

vim.snippet.add("list", s("List", {
    i(1, "3"),
    d(2, function(args)
        local n = tonumber(args[1]) or 1
        local nodes = {}
        for j = 1, n do
            table.insert(nodes, t("\n- "))
            table.insert(nodes, i(j, "item " .. j))
        end
        return sn(nil, nodes)
    end, { 1 }),
}))
-- Typing "3" in field 1 → generates 3 editable list items

r() — restore node: Used inside d() to preserve user edits when the dynamic node regenerates.

d(2, function(args, parent, old_state)
    return sn(nil, {
        t("\n"),
        i(1, "editable"),  -- restored from old_state if available
    }, { stored = old_state })
end, { 1 })

Dynamic snippets have a 50ms debounce on recomputation to avoid UI jank. Lua functions are time-guarded at 100ms — if a function exceeds this limit, recomputation is skipped for that cycle.

Settings

Settings → Vim Motions → Snippets

SettingDefaultDescription
Enable snippetstrueMaster toggle for snippet expansion
Bundled snippetstrueInclude built-in Obsidian markdown snippets
Snippet directory(empty)Path to user snippet JSON directory
Trigger modebothcompletion, tab, or both

Vimrc options

set snippets        " Enable/disable snippets (boolean)
set snippetbundled  " Enable/disable bundled snippets (boolean)
set snippetdir=path " Set user snippet directory
set snippettrigger=both " Set trigger mode (completion/tab/both)