
Obsidian Nvim
- 103 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Access and manage Obsidian vault from Neovim editor
About
Integrates Obsidian vault management with Neovim. Used by developers who want to edit and manage knowledge bases while staying in their editor.
- Obsidian plugin for Neovim
- Vault management from terminal
Obsidian Nvim by the numbers
- 103 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,358 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill obsidian-nvimAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 103 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Access and manage Obsidian vault from Neovim editor
Files
obsidian.nvim Skill
A comprehensive guide for implementing and configuring obsidian.nvim - the Neovim plugin for managing Obsidian vaults.
Quick Start
Minimal Installation (lazy.nvim)
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "personal", path = "~/vaults/personal" },
},
},
}System Requirements
- Neovim: >= 0.10.0
- ripgrep: Required for completion and search (
brew install ripgrep) - pngpaste (macOS): For image pasting (
brew install pngpaste) - xclip/wl-clipboard (Linux): For image pasting
Configuration
See references/configuration.md for complete configuration options.
Essential Configuration Options
require("obsidian").setup({
-- Workspace configuration (required)
workspaces = {
{ name = "personal", path = "~/vaults/personal" },
{ name = "work", path = "~/vaults/work" },
},
-- Daily notes
daily_notes = {
folder = "daily",
date_format = "%Y-%m-%d",
alias_format = "%B %-d, %Y",
default_tags = { "daily-notes" },
},
-- Templates
templates = {
folder = "templates",
date_format = "%Y-%m-%d",
time_format = "%H:%M",
},
-- Note ID generation (Zettelkasten-style by default)
note_id_func = function(title)
local suffix = ""
if title ~= nil then
suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
else
for _ = 1, 4 do
suffix = suffix .. string.char(math.random(65, 90))
end
end
return tostring(os.time()) .. "-" .. suffix
end,
-- Completion settings
completion = {
nvim_cmp = true, -- or blink = true for blink.cmp
min_chars = 2,
},
-- UI customization
ui = {
enable = true,
checkboxes = {
[" "] = { char = "", hl_group = "ObsidianTodo" },
["x"] = { char = "", hl_group = "ObsidianDone" },
},
},
})Commands
See references/commands.md for complete command reference.
Primary Commands
| Command | Description |
|---|---|
:Obsidian | Open command picker |
:Obsidian today [OFFSET] | Open/create daily note |
:Obsidian new [TITLE] | Create new note |
:Obsidian search [QUERY] | Search vault with ripgrep |
:Obsidian quick_switch | Fuzzy find notes |
:Obsidian backlinks | Show references to current note |
:Obsidian template [NAME] | Insert template |
:Obsidian workspace [NAME] | Switch workspace |
Visual Mode Commands
| Command | Description |
|---|---|
:Obsidian link [QUERY] | Link selection to existing note |
:Obsidian link_new [TITLE] | Create note and link selection |
:Obsidian extract_note [TITLE] | Extract selection to new note |
Keymaps
Smart Action (Recommended: <CR>)
vim.keymap.set("n", "<CR>", function()
if require("obsidian").util.cursor_on_markdown_link() then
return "<cmd>Obsidian follow_link<CR>"
else
return "<CR>"
end
end, { expr = true })Navigation Links
vim.keymap.set("n", "[o", function()
require("obsidian").util.nav_link("prev")
end, { buffer = true, desc = "Previous link" })
vim.keymap.set("n", "]o", function()
require("obsidian").util.nav_link("next")
end, { buffer = true, desc = "Next link" })Picker Integration
Configure your preferred picker:
picker = {
name = "telescope", -- or "fzf-lua", "mini.pick", "snacks.picker"
note_mappings = {
new = "<C-x>",
insert_link = "<C-l>",
},
tag_mappings = {
tag_note = "<C-x>",
insert_tag = "<C-l>",
},
},Completion Integration
With blink.cmp
completion = {
blink = true,
nvim_cmp = false,
min_chars = 2,
},With nvim-cmp
completion = {
nvim_cmp = true,
blink = false,
min_chars = 2,
},Triggers:
[[- Wiki link completion[- Markdown link completion#- Tag completion
Templates
Template Variables
| Variable | Description |
|---|---|
{{title}} | Note title |
{{date}} | Current date |
{{time}} | Current time |
{{id}} | Note ID |
Custom Substitutions
templates = {
folder = "templates",
substitutions = {
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end,
tomorrow = function()
return os.date("%Y-%m-%d", os.time() + 86400)
end,
},
},Frontmatter Management
frontmatter = {
enabled = true,
func = function(note)
local out = { id = note.id, aliases = note.aliases, tags = note.tags }
if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
for k, v in pairs(note.metadata) do
out[k] = v
end
end
return out
end,
sort = { "id", "aliases", "tags" },
},Troubleshooting
See references/troubleshooting.md for comprehensive troubleshooting.
Health Check
:checkhealth obsidianQuick Fixes
| Issue | Solution |
|---|---|
| Completion not working | Install ripgrep: brew install ripgrep |
| Picker not opening | Verify picker name matches installed plugin |
| Images not pasting | macOS: brew install pngpaste |
| Links not following | Ensure cursor is on [[link]] or [text](link) |
| Checkboxes not rendering | Set :set conceallevel=2 |
| Workspace not found | Verify path exists and file is inside vault |
Debug Mode
log_level = vim.log.levels.DEBUG,Examples
See references/examples.md for practical configuration examples.
Resources
---
Gotchas
- The plugin requires Obsidian to be running for live updates — vault changes from Neovim don't propagate to Obsidian's index until the app is open AND has focused the vault.
- Completion source ordering conflicts with `cmp-buffer` — LSP completion may not show note suggestions if buffer source ranks higher; explicit
sorting.priority_weightadjustment needed. - Daily-note template paths use Lua's `os.date` format, not Obsidian's natural-language date format —
%Y-%m-%din nvim,YYYY-MM-DDin Obsidian. - `require("obsidian").get_client():resolve_link()` returns nil for an unindexed file — even if the file exists. Force a
:ObsidianRefreshafter creating notes outside the plugin. - lazy.nvim lazy-loading on `BufRead`: the first vault file you open before plugin spec evaluation doesn't get note features — set
lazy=falsefor obsidian.nvim or use aVeryLazyevent.
obsidian.nvim Commands Reference
Complete reference for all obsidian.nvim commands.
Command Entry Point
The main command is :Obsidian with subcommands:
:Obsidian " Open command picker
:Obsidian <Tab> " Tab-complete subcommands
:Obsidian <subcommand> " Execute specific subcommandWorkspace Commands
workspace
Switch between configured workspaces or check current workspace.
:Obsidian workspace " Show current workspace
:Obsidian workspace personal " Switch to 'personal' workspace
:Obsidian workspace work " Switch to 'work' workspaceDaily Notes Commands
today
Open or create today's daily note.
:Obsidian today " Open today's daily note
:Obsidian today -1 " Open yesterday's daily note
:Obsidian today 1 " Open tomorrow's daily note
:Obsidian today -7 " Open note from 7 days agoNote: Unlike yesterday and tomorrow, this does not skip weekends.
yesterday
Open the daily note for the previous working day (skips weekends by default).
:Obsidian yesterdaytomorrow
Open the daily note for the next working day (skips weekends by default).
:Obsidian tomorrowdailies
Browse daily notes with a picker.
:Obsidian dailies " List all daily notes
:Obsidian dailies -7 0 " List notes from past week to today
:Obsidian dailies -2 1 " List from 2 days ago to tomorrowNote Creation Commands
new
Create a new note with optional title.
:Obsidian new " Create note (prompts for title)
:Obsidian new My New Note " Create note with titlenew_from_template
Create a new note from a template.
:Obsidian new_from_template " Select both from picker
:Obsidian new_from_template "My Note" " Note title, select template
:Obsidian new_from_template "My Note" meeting " Specify bothNavigation Commands
quick_switch
Fuzzy find and switch to another note.
:Obsidian quick_switchfollow_link
Follow a reference or link under the cursor.
:Obsidian follow_link " Open in current window
:Obsidian follow_link vsplit " Open in vertical split
:Obsidian follow_link hsplit " Open in horizontal split
:Obsidian follow_link vsplit_force " Force vertical split (new window)
:Obsidian follow_link hsplit_force " Force horizontal split (new window)open
Open a note in the Obsidian app.
:Obsidian open " Open current note
:Obsidian open query " Open note matching querySearch Commands
search
Search for notes using ripgrep.
:Obsidian search " Open search picker
:Obsidian search query " Search for 'query'tags
Find notes with specific tags.
:Obsidian tags " Show all tags
:Obsidian tags daily-notes " Find notes with #daily-notes
:Obsidian tags work project " Find notes with #work or #projectNote Information Commands
backlinks
Show references to the current note.
:Obsidian backlinksAlternative: grr or vim.lsp.buf.references() for quickfix list.
links
List all links in the current note.
:Obsidian linkstoc
Show table of contents for current note.
:Obsidian tocTemplate Commands
template
Insert a template at cursor position.
:Obsidian template " Select template from picker
:Obsidian template daily " Insert specific templateLink Commands (Normal Mode)
rename
Rename current note and update all backlinks.
:Obsidian rename " Prompt for new name
:Obsidian rename "New Name" " Rename to specific nameImportant:
- Runs
:wabefore renaming - Loads all affected notes into buffer list
- Run
:waagain after renaming
Alternative: grn or vim.lsp.buf.rename()
Link Commands (Visual Mode)
link
Link selected text to an existing note.
:'<,'>Obsidian link " Search for note
:'<,'>Obsidian link query " Link to note matching querylink_new
Create a new note and link the selected text to it.
:'<,'>Obsidian link_new " Use selection as title
:'<,'>Obsidian link_new "New Title" " Specify titleextract_note
Extract selected text to a new note and replace with link.
:'<,'>Obsidian extract_note " Prompt for title
:'<,'>Obsidian extract_note "Extracted" " Specify titleCheckbox Commands
toggle_checkbox
Cycle through checkbox states.
:Obsidian toggle_checkboxCycles through: [ ] -> [x] -> [~] -> [!] -> [>] -> [ ]
Supports range selection for multiple lines.
Image Commands
paste_img
Paste an image from clipboard into the note.
:Obsidian paste_img " Auto-generate filename
:Obsidian paste_img myimage " Specify filenameRequirements:
- macOS:
pngpaste(brew install pngpaste) - Linux:
xclip(X11) orwl-clipboard(Wayland)
Utility Commands
check
Check for issues in your vault.
:Obsidian checkAlso available via :checkhealth obsidian
Legacy Commands
If legacy_commands = true is set, these commands are also available:
| Legacy Command | New Command |
|---|---|
:ObsidianToday | :Obsidian today |
:ObsidianYesterday | :Obsidian yesterday |
:ObsidianTomorrow | :Obsidian tomorrow |
:ObsidianDailies | :Obsidian dailies |
:ObsidianNew | :Obsidian new |
:ObsidianOpen | :Obsidian open |
:ObsidianBacklinks | :Obsidian backlinks |
:ObsidianTags | :Obsidian tags |
:ObsidianSearch | :Obsidian search |
:ObsidianTemplate | :Obsidian template |
:ObsidianNewFromTemplate | :Obsidian new_from_template |
:ObsidianQuickSwitch | :Obsidian quick_switch |
:ObsidianLinkNew | :Obsidian link_new |
:ObsidianLink | :Obsidian link |
:ObsidianLinks | :Obsidian links |
:ObsidianFollowLink | :Obsidian follow_link |
:ObsidianToggleCheckbox | :Obsidian toggle_checkbox |
:ObsidianWorkspace | :Obsidian workspace |
:ObsidianRename | :Obsidian rename |
:ObsidianPasteImg | :Obsidian paste_img |
:ObsidianExtractNote | :Obsidian extract_note |
:ObsidianTOC | :Obsidian toc |
Command Mappings
Recommended Keymaps
-- Daily notes
vim.keymap.set("n", "<leader>ot", "<cmd>Obsidian today<CR>", { desc = "Today's note" })
vim.keymap.set("n", "<leader>oy", "<cmd>Obsidian yesterday<CR>", { desc = "Yesterday's note" })
vim.keymap.set("n", "<leader>om", "<cmd>Obsidian tomorrow<CR>", { desc = "Tomorrow's note" })
vim.keymap.set("n", "<leader>od", "<cmd>Obsidian dailies<CR>", { desc = "Daily notes" })
-- Note operations
vim.keymap.set("n", "<leader>on", "<cmd>Obsidian new<CR>", { desc = "New note" })
vim.keymap.set("n", "<leader>os", "<cmd>Obsidian search<CR>", { desc = "Search notes" })
vim.keymap.set("n", "<leader>oq", "<cmd>Obsidian quick_switch<CR>", { desc = "Quick switch" })
vim.keymap.set("n", "<leader>ob", "<cmd>Obsidian backlinks<CR>", { desc = "Backlinks" })
vim.keymap.set("n", "<leader>ol", "<cmd>Obsidian links<CR>", { desc = "Links" })
vim.keymap.set("n", "<leader>oc", "<cmd>Obsidian toc<CR>", { desc = "Table of contents" })
-- Templates
vim.keymap.set("n", "<leader>oi", "<cmd>Obsidian template<CR>", { desc = "Insert template" })
vim.keymap.set("n", "<leader>oN", "<cmd>Obsidian new_from_template<CR>", { desc = "New from template" })
-- Visual mode
vim.keymap.set("v", "<leader>ol", "<cmd>Obsidian link<CR>", { desc = "Link selection" })
vim.keymap.set("v", "<leader>oL", "<cmd>Obsidian link_new<CR>", { desc = "Link new" })
vim.keymap.set("v", "<leader>oe", "<cmd>Obsidian extract_note<CR>", { desc = "Extract note" })
-- Checkbox
vim.keymap.set("n", "<leader>ox", "<cmd>Obsidian toggle_checkbox<CR>", { desc = "Toggle checkbox" })
-- Smart action (follow link or toggle checkbox)
vim.keymap.set("n", "<CR>", function()
local obsidian = require("obsidian")
if obsidian.util.cursor_on_markdown_link() then
return "<cmd>Obsidian follow_link<CR>"
else
return "<CR>"
end
end, { expr = true })Context-Sensitive Commands
Commands are context-aware:
- Some commands only appear when in a note (markdown file)
- Some commands only appear in visual mode
- Tab completion filters by context
Note Commands (require being in a note)
backlinksfollow_linklinkspaste_imgrenametemplatetoctoggle_checkbox
Visual Mode Commands
extract_notelinklink_new
Always Available Commands
checkdailiesnewnew_from_templateopenquick_switchsearchtagstodaytomorrowworkspaceyesterday
obsidian.nvim Configuration Reference
Complete configuration options for obsidian.nvim.
Core Configuration
Workspaces
Required configuration for vault locations:
workspaces = {
{
name = "personal",
path = "~/vaults/personal",
-- Optional: workspace-specific overrides
overrides = {
notes_subdir = "notes",
},
},
{
name = "work",
path = "~/vaults/work",
},
},Note Location
-- Subdirectory for new notes (nil = vault root)
notes_subdir = "notes",
-- Where to create new notes
-- Options: "current_dir", "notes_subdir"
new_notes_location = "current_dir",Note ID Function
-- Default: Zettelkasten-style with timestamp
note_id_func = function(title)
local suffix = ""
if title ~= nil then
suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
else
for _ = 1, 4 do
suffix = suffix .. string.char(math.random(65, 90))
end
end
return tostring(os.time()) .. "-" .. suffix
end,
-- Simple title-based ID
note_id_func = function(title)
if title ~= nil then
return title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
end
return tostring(os.time())
end,Note Path Function
note_path_func = function(spec)
local path = spec.dir / tostring(spec.id)
return path
end,Daily Notes
daily_notes = {
-- Folder for daily notes (relative to vault)
folder = "daily",
-- Date format for filenames (strftime format)
date_format = "%Y-%m-%d",
-- Alias format for display
alias_format = "%B %-d, %Y",
-- Template file for daily notes
template = "daily.md",
-- Default tags for daily notes
default_tags = { "daily-notes" },
-- Skip weekends for yesterday/tomorrow commands
workdays_only = true,
},Templates
templates = {
-- Template folder (relative to vault)
folder = "templates",
-- Date format for {{date}} variable
date_format = "%Y-%m-%d",
-- Time format for {{time}} variable
time_format = "%H:%M",
-- Custom substitution variables
substitutions = {
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end,
tomorrow = function()
return os.date("%Y-%m-%d", os.time() + 86400)
end,
week_number = function()
return os.date("%V")
end,
},
-- Template-specific customizations
customizations = {
["meeting"] = {
notes_subdir = "meetings",
note_id_func = function(title)
return os.date("%Y%m%d") .. "-" .. (title or "meeting")
end,
},
},
},Template Variables
| Variable | Description | Example |
|---|---|---|
{{title}} | Note title | "My Note" |
{{date}} | Current date | "2024-01-15" |
{{time}} | Current time | "14:30" |
{{id}} | Note ID | "1705344600-my-note" |
{{path}} | Note path | "notes/my-note.md" |
Frontmatter
frontmatter = {
-- Enable/disable frontmatter management
enabled = true,
-- Can be a function for conditional enabling
-- enabled = function(fname) return not fname:match("templates/") end,
-- Function to generate frontmatter content
func = function(note)
local out = {
id = note.id,
aliases = note.aliases,
tags = note.tags,
}
-- Include custom metadata
if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then
for k, v in pairs(note.metadata) do
out[k] = v
end
end
return out
end,
-- Property sort order (list or function)
sort = { "id", "aliases", "tags" },
-- sort = false, -- disable sorting
},Completion
completion = {
-- Use nvim-cmp for completion
nvim_cmp = true,
-- Use blink.cmp for completion (takes precedence if both true)
blink = false,
-- Minimum characters to trigger completion
min_chars = 2,
-- Case-sensitive matching
match_case = true,
-- Allow creating new notes from completion
create_new = true,
},Picker Configuration
picker = {
-- Picker backend: "telescope", "fzf-lua", "mini.pick", "snacks.picker"
name = "telescope",
-- Mappings in note picker
note_mappings = {
-- Create new note
new = "<C-x>",
-- Insert link to selected note
insert_link = "<C-l>",
},
-- Mappings in tag picker
tag_mappings = {
-- Create note from tag
tag_note = "<C-x>",
-- Insert tag
insert_tag = "<C-l>",
},
},Search Options
search = {
-- Sort by: "modified", "created", "path"
sort_by = "modified",
-- Reverse sort order
sort_reversed = true,
-- Maximum lines to search per file
max_lines = 1000,
},Link Style
-- Preferred link style: "wiki" or "markdown"
preferred_link_style = "wiki",
-- Wiki link format function
wiki_link_func = function(opts)
if opts.id == opts.label then
return string.format("[[%s]]", opts.label)
else
return string.format("[[%s|%s]]", opts.id, opts.label)
end
end,
-- Markdown link format function
markdown_link_func = function(opts)
return string.format("[%s](%s)", opts.label, opts.path)
end,UI Configuration
ui = {
-- Enable/disable UI features
enable = true,
-- Suppress conceallevel warning
ignore_conceal_warn = false,
-- Debounce time for UI updates (ms)
update_debounce = 200,
-- Disable UI for files larger than this
max_file_length = 5000,
-- Checkbox styles
checkboxes = {
[" "] = { char = "", hl_group = "ObsidianTodo" },
["~"] = { char = "", hl_group = "ObsidianTilde" },
["!"] = { char = "", hl_group = "ObsidianImportant" },
[">"] = { char = "", hl_group = "ObsidianRightArrow" },
["x"] = { char = "", hl_group = "ObsidianDone" },
},
-- Bullet point style
bullets = { char = "•", hl_group = "ObsidianBullet" },
-- External link icon
external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
-- Reference text style
reference_text = { hl_group = "ObsidianRefText" },
-- Highlighted text style
highlight_text = { hl_group = "ObsidianHighlightText" },
-- Tag style
tags = { hl_group = "ObsidianTag" },
-- Block ID style
block_ids = { hl_group = "ObsidianBlockID" },
-- Custom highlight groups
hl_groups = {
ObsidianTodo = { bold = true, fg = "#f78c6c" },
ObsidianDone = { bold = true, fg = "#89ddff" },
ObsidianRightArrow = { bold = true, fg = "#f78c6c" },
ObsidianTilde = { strikethrough = true, fg = "#89ddff" },
ObsidianImportant = { bold = true, fg = "#d73128" },
ObsidianBullet = { bold = true, fg = "#89ddff" },
ObsidianRefText = { underline = true, fg = "#c792ea" },
ObsidianExtLinkIcon = { fg = "#c792ea" },
ObsidianTag = { italic = true, fg = "#89ddff" },
ObsidianBlockID = { italic = true, fg = "#89ddff" },
ObsidianHighlightText = { bg = "#75662e" },
},
},Statusline
statusline = {
-- Enable statusline component
enabled = true,
-- Format string with placeholders
format = "{{backlinks}} backlinks {{properties}} properties {{words}} words {{chars}} chars",
},Backlinks
backlinks = {
-- Parse headers in backlink display
parse_headers = true,
},Attachments
Configure image and file attachment handling:
attachments = {
-- Folder for images/attachments (relative to vault)
img_folder = "assets/imgs",
-- Confirm before creating new attachments folder
confirm_img_paste = true,
-- Function to generate image filename
img_name_func = function()
return string.format("%s-", os.time())
end,
-- Function to generate image text/link
img_text_func = function(client, path)
path = client:vault_relative_path(path) or path
return string.format("", path.name, path)
end,
},Image Name Patterns
-- Timestamp-based (default)
img_name_func = function()
return string.format("%s-", os.time())
end,
-- UUID-based
img_name_func = function()
local uuid = ""
for _ = 1, 8 do
uuid = uuid .. string.format("%x", math.random(0, 15))
end
return uuid .. "-"
end,
-- Date-based with description prompt
img_name_func = function()
local desc = vim.fn.input("Image description: ")
local date = os.date("%Y%m%d")
if desc ~= "" then
return date .. "-" .. desc:gsub(" ", "-"):lower() .. "-"
end
return date .. "-"
end,Callbacks
Lifecycle hooks for custom behavior:
callbacks = {
-- Called after obsidian.nvim is fully initialized
post_setup = function(client)
-- Example: Set up additional keymaps
vim.notify("Obsidian workspace: " .. client.current_workspace.name)
end,
-- Called when entering a note buffer
enter_note = function(client, note)
-- Example: Update statusline, log access
vim.b.obsidian_note_id = note.id
end,
-- Called when leaving a note buffer
leave_note = function(client, note)
-- Example: Auto-save, cleanup
end,
-- Called before writing/saving a note
pre_write_note = function(client, note)
-- Example: Update modified timestamp in frontmatter
note.metadata = note.metadata or {}
note.metadata.modified = os.date("%Y-%m-%d %H:%M")
end,
-- Called after switching workspaces
post_set_workspace = function(client, workspace)
vim.notify("Switched to workspace: " .. workspace.name)
end,
},Callback Use Cases
-- Auto-update modified date
callbacks = {
pre_write_note = function(client, note)
if note.metadata then
note.metadata.modified = os.date("%Y-%m-%d %H:%M")
end
end,
},
-- Log note access for analytics
callbacks = {
enter_note = function(client, note)
local log_file = client.dir / ".note_access.log"
local f = io.open(tostring(log_file), "a")
if f then
f:write(os.date("%Y-%m-%d %H:%M") .. " " .. note.id .. "\n")
f:close()
end
end,
},
-- Workspace-specific settings
callbacks = {
post_set_workspace = function(client, workspace)
if workspace.name == "work" then
vim.opt_local.spell = true
vim.opt_local.spelllang = "en_us"
end
end,
},Note Opening
-- How to open notes: "current", "vsplit", "hsplit"
open_notes_in = "current",
-- Function to handle URL following
follow_url_func = vim.ui.open,
-- Function to handle image following
follow_img_func = vim.ui.open,Logging
-- Log level: vim.log.levels.DEBUG, INFO, WARN, ERROR
log_level = vim.log.levels.INFO,Legacy Commands
-- Enable legacy :ObsidianXXX commands (deprecated, will be removed)
legacy_commands = false,Complete Example
require("obsidian").setup({
workspaces = {
{ name = "personal", path = "~/vaults/personal" },
{ name = "work", path = "~/vaults/work" },
},
notes_subdir = "notes",
new_notes_location = "notes_subdir",
daily_notes = {
folder = "daily",
date_format = "%Y-%m-%d",
alias_format = "%B %-d, %Y",
template = "daily.md",
default_tags = { "daily-notes" },
workdays_only = true,
},
templates = {
folder = "templates",
date_format = "%Y-%m-%d",
time_format = "%H:%M",
substitutions = {},
},
frontmatter = {
enabled = true,
sort = { "id", "aliases", "tags" },
},
completion = {
nvim_cmp = true,
min_chars = 2,
},
picker = {
name = "telescope",
note_mappings = {
new = "<C-x>",
insert_link = "<C-l>",
},
},
preferred_link_style = "wiki",
open_notes_in = "current",
ui = {
enable = true,
checkboxes = {
[" "] = { char = "", hl_group = "ObsidianTodo" },
["x"] = { char = "", hl_group = "ObsidianDone" },
},
},
log_level = vim.log.levels.INFO,
})obsidian.nvim Configuration Examples
Practical configuration examples for common use cases.
Minimal Setup
The simplest possible configuration:
-- lazy.nvim
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
},
}Full-Featured Setup
Complete configuration with all common features:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
lazy = true,
ft = "markdown",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-telescope/telescope.nvim",
"hrsh7th/nvim-cmp",
},
opts = {
workspaces = {
{
name = "personal",
path = "~/vaults/personal",
},
{
name = "work",
path = "~/vaults/work",
overrides = {
notes_subdir = "notes",
},
},
},
notes_subdir = "inbox",
new_notes_location = "notes_subdir",
daily_notes = {
folder = "daily",
date_format = "%Y-%m-%d",
alias_format = "%B %-d, %Y",
template = "daily.md",
default_tags = { "daily" },
workdays_only = false,
},
templates = {
folder = "templates",
date_format = "%Y-%m-%d",
time_format = "%H:%M",
substitutions = {
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end,
tomorrow = function()
return os.date("%Y-%m-%d", os.time() + 86400)
end,
},
},
completion = {
nvim_cmp = true,
min_chars = 2,
},
picker = {
name = "telescope",
note_mappings = {
new = "<C-x>",
insert_link = "<C-l>",
},
tag_mappings = {
tag_note = "<C-x>",
insert_tag = "<C-l>",
},
},
preferred_link_style = "wiki",
open_notes_in = "current",
ui = {
enable = true,
update_debounce = 200,
checkboxes = {
[" "] = { char = "", hl_group = "ObsidianTodo" },
["x"] = { char = "", hl_group = "ObsidianDone" },
[">"] = { char = "", hl_group = "ObsidianRightArrow" },
["~"] = { char = "", hl_group = "ObsidianTilde" },
},
bullets = { char = "•", hl_group = "ObsidianBullet" },
external_link_icon = { char = "", hl_group = "ObsidianExtLinkIcon" },
},
statusline = {
enabled = true,
format = "{{backlinks}} {{words}} words",
},
log_level = vim.log.levels.INFO,
},
keys = {
{ "<leader>oo", "<cmd>Obsidian<CR>", desc = "Obsidian commands" },
{ "<leader>ot", "<cmd>Obsidian today<CR>", desc = "Today's note" },
{ "<leader>os", "<cmd>Obsidian search<CR>", desc = "Search notes" },
{ "<leader>oq", "<cmd>Obsidian quick_switch<CR>", desc = "Quick switch" },
{ "<leader>on", "<cmd>Obsidian new<CR>", desc = "New note" },
{ "<leader>ob", "<cmd>Obsidian backlinks<CR>", desc = "Backlinks" },
},
}With blink.cmp
Using blink.cmp instead of nvim-cmp:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
dependencies = {
"nvim-lua/plenary.nvim",
"saghen/blink.cmp",
},
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
completion = {
blink = true,
nvim_cmp = false,
min_chars = 2,
},
},
}With fzf-lua
Using fzf-lua as the picker:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
dependencies = {
"nvim-lua/plenary.nvim",
"ibhagwan/fzf-lua",
},
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
picker = {
name = "fzf-lua",
},
},
}With snacks.nvim
Using snacks.picker and snacks.image:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
dependencies = {
"nvim-lua/plenary.nvim",
"folke/snacks.nvim",
},
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
picker = {
name = "snacks.picker",
},
},
}Zettelkasten Setup
Configuration optimized for Zettelkasten method:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "zettelkasten", path = "~/zettelkasten" },
},
-- Zettelkasten-style note IDs with timestamp
note_id_func = function(title)
local suffix = ""
if title ~= nil then
suffix = title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
else
for _ = 1, 4 do
suffix = suffix .. string.char(math.random(65, 90))
end
end
return tostring(os.time()) .. "-" .. suffix
end,
-- All notes in flat structure
notes_subdir = nil,
new_notes_location = "current_dir",
-- Minimal frontmatter
frontmatter = {
enabled = true,
func = function(note)
return {
id = note.id,
tags = note.tags,
created = os.date("%Y-%m-%d %H:%M"),
}
end,
},
-- Wiki links for internal linking
preferred_link_style = "wiki",
},
}PARA Method Setup
Configuration for PARA organization (Projects, Areas, Resources, Archive):
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "para", path = "~/para" },
},
-- Human-readable note IDs
note_id_func = function(title)
if title ~= nil then
return title:gsub(" ", "-"):gsub("[^A-Za-z0-9-]", ""):lower()
end
return "note-" .. os.date("%Y%m%d%H%M%S")
end,
-- Notes in inbox for processing
notes_subdir = "inbox",
new_notes_location = "notes_subdir",
daily_notes = {
folder = "areas/daily",
date_format = "%Y-%m-%d",
},
templates = {
folder = "resources/templates",
customizations = {
project = {
notes_subdir = "projects",
},
area = {
notes_subdir = "areas",
},
resource = {
notes_subdir = "resources",
},
},
},
},
}Multiple Vaults with Overrides
Managing multiple vaults with different settings:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{
name = "personal",
path = "~/vaults/personal",
overrides = {
notes_subdir = "notes",
daily_notes = {
folder = "journal",
date_format = "%Y/%m/%Y-%m-%d",
template = "journal.md",
},
},
},
{
name = "work",
path = "~/vaults/work",
overrides = {
notes_subdir = "notes",
daily_notes = {
folder = "standup",
date_format = "%Y-%m-%d",
template = "standup.md",
workdays_only = true,
},
templates = {
folder = "templates",
customizations = {
meeting = {
notes_subdir = "meetings",
},
},
},
},
},
},
},
}Custom Keymaps
Complete keymap configuration:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
},
keys = {
-- Command picker
{ "<leader>oo", "<cmd>Obsidian<CR>", desc = "Commands" },
-- Daily notes
{ "<leader>ot", "<cmd>Obsidian today<CR>", desc = "Today" },
{ "<leader>oy", "<cmd>Obsidian yesterday<CR>", desc = "Yesterday" },
{ "<leader>om", "<cmd>Obsidian tomorrow<CR>", desc = "Tomorrow" },
{ "<leader>od", "<cmd>Obsidian dailies<CR>", desc = "Daily notes" },
-- Notes
{ "<leader>on", "<cmd>Obsidian new<CR>", desc = "New note" },
{ "<leader>oN", "<cmd>Obsidian new_from_template<CR>", desc = "New from template" },
{ "<leader>os", "<cmd>Obsidian search<CR>", desc = "Search" },
{ "<leader>oq", "<cmd>Obsidian quick_switch<CR>", desc = "Quick switch" },
{ "<leader>og", "<cmd>Obsidian tags<CR>", desc = "Tags" },
-- Current note
{ "<leader>ob", "<cmd>Obsidian backlinks<CR>", desc = "Backlinks" },
{ "<leader>ol", "<cmd>Obsidian links<CR>", desc = "Links" },
{ "<leader>oc", "<cmd>Obsidian toc<CR>", desc = "TOC" },
{ "<leader>oi", "<cmd>Obsidian template<CR>", desc = "Insert template" },
{ "<leader>or", "<cmd>Obsidian rename<CR>", desc = "Rename" },
{ "<leader>ox", "<cmd>Obsidian toggle_checkbox<CR>", desc = "Toggle checkbox" },
{ "<leader>op", "<cmd>Obsidian paste_img<CR>", desc = "Paste image" },
-- Visual mode
{ "<leader>ol", "<cmd>Obsidian link<CR>", mode = "v", desc = "Link selection" },
{ "<leader>oL", "<cmd>Obsidian link_new<CR>", mode = "v", desc = "Link to new" },
{ "<leader>oe", "<cmd>Obsidian extract_note<CR>", mode = "v", desc = "Extract note" },
-- Workspace
{ "<leader>ow", "<cmd>Obsidian workspace<CR>", desc = "Workspace" },
},
config = function(_, opts)
require("obsidian").setup(opts)
-- Smart enter key
vim.keymap.set("n", "<CR>", function()
local obsidian = require("obsidian")
if obsidian.util.cursor_on_markdown_link() then
return "<cmd>Obsidian follow_link<CR>"
else
return "<CR>"
end
end, { expr = true, buffer = true })
-- Navigate links
vim.keymap.set("n", "[o", function()
require("obsidian").util.nav_link("prev")
end, { buffer = true, desc = "Previous link" })
vim.keymap.set("n", "]o", function()
require("obsidian").util.nav_link("next")
end, { buffer = true, desc = "Next link" })
end,
}Custom Template Substitutions
Advanced template variable customizations:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
templates = {
folder = "templates",
date_format = "%Y-%m-%d",
time_format = "%H:%M",
substitutions = {
-- Previous/next days
yesterday = function()
return os.date("%Y-%m-%d", os.time() - 86400)
end,
tomorrow = function()
return os.date("%Y-%m-%d", os.time() + 86400)
end,
-- Week info
week_number = function()
return os.date("%V")
end,
week_start = function()
local today = os.time()
local wday = os.date("*t", today).wday
local days_since_monday = (wday + 5) % 7
return os.date("%Y-%m-%d", today - days_since_monday * 86400)
end,
week_end = function()
local today = os.time()
local wday = os.date("*t", today).wday
local days_until_sunday = (7 - wday) % 7
return os.date("%Y-%m-%d", today + days_until_sunday * 86400)
end,
-- Month info
month_name = function()
return os.date("%B")
end,
month_start = function()
return os.date("%Y-%m-01")
end,
month_end = function()
local t = os.date("*t")
t.month = t.month + 1
t.day = 0
return os.date("%Y-%m-%d", os.time(t))
end,
-- Random quote (example)
random_quote = function()
local quotes = {
"The only way to do great work is to love what you do.",
"Stay hungry, stay foolish.",
"Think different.",
}
return quotes[math.random(#quotes)]
end,
},
},
},
}Custom Frontmatter
Advanced frontmatter customization:
return {
"obsidian-nvim/obsidian.nvim",
version = "*",
ft = "markdown",
opts = {
workspaces = {
{ name = "notes", path = "~/notes" },
},
frontmatter = {
enabled = true,
func = function(note)
local out = {
id = note.id,
aliases = note.aliases,
tags = note.tags,
created = note.metadata.created or os.date("%Y-%m-%d %H:%M"),
modified = os.date("%Y-%m-%d %H:%M"),
}
-- Preserve existing metadata
if note.metadata then
for k, v in pairs(note.metadata) do
if out[k] == nil then
out[k] = v
end
end
end
return out
end,
sort = { "id", "aliases", "tags", "created", "modified" },
},
},
}Template Files
Daily Note Template (templates/daily.md)
---
id: {{id}}
aliases:
- {{date}}
tags:
- daily
created: {{date}} {{time}}
---
# {{title}}
## Morning
- [ ] Review calendar
- [ ] Check emails
- [ ] Plan priorities
## Tasks
- [ ]
## Notes
## Evening Review
### What went well?
### What could improve?
### Tomorrow's focus
---
[[{{yesterday}}|← Yesterday]] | [[{{tomorrow}}|Tomorrow →]]Meeting Template (templates/meeting.md)
---
id: {{id}}
aliases: []
tags:
- meeting
created: {{date}} {{time}}
attendees: []
---
# {{title}}
**Date:** {{date}}
**Time:** {{time}}
**Attendees:**
## Agenda
1.
## Notes
## Action Items
- [ ]
## Follow-upProject Template (templates/project.md)
---
id: {{id}}
aliases: []
tags:
- project
status: active
created: {{date}}
---
# {{title}}
## Overview
## Goals
-
## Tasks
- [ ]
## Resources
## Notes
## Timeline
| Milestone | Target Date | Status |
|-----------|-------------|--------|
| | | |obsidian.nvim Troubleshooting Guide
Comprehensive solutions for common issues with obsidian.nvim.
Health Check
Always start by running the health check:
:checkhealth obsidianThis verifies:
- Neovim version compatibility
- Required dependencies (ripgrep, pngpaste/xclip)
- Workspace configuration
- Plugin dependencies
Installation Issues
Plugin Not Loading
Symptoms: Commands not available, no syntax highlighting
Solutions:
1. Verify lazy loading trigger:
-- Ensure ft = "markdown" is set
return {
"obsidian-nvim/obsidian.nvim",
ft = "markdown", -- Required for lazy loading
-- ...
}2. Check if in vault directory:
:pwd
:echo expand('%:p')The file must be within a configured workspace path.
3. Force load the plugin:
:Lazy load obsidian.nvimWorkspace Not Detected
Symptoms: "No workspace found" errors
Solutions:
1. Verify workspace paths exist:
workspaces = {
{ name = "personal", path = vim.fn.expand("~/vaults/personal") },
}2. Check path expansion:
:lua print(vim.fn.expand("~/vaults/personal"))3. Ensure you're editing a file inside the vault:
:echo expand('%:p:h')Completion Issues
Wiki Link Completion Not Working
Symptoms: Typing [[ doesn't trigger completion
Solutions:
1. Verify ripgrep is installed:
which rg
rg --version2. Check completion configuration:
completion = {
nvim_cmp = true, -- or blink = true
min_chars = 2,
},3. For nvim-cmp, verify source is added:
-- In your nvim-cmp config
sources = {
{ name = "obsidian" },
{ name = "obsidian_new" },
{ name = "obsidian_tags" },
-- other sources...
}4. For blink.cmp:
completion = {
blink = true,
nvim_cmp = false,
},Tag Completion Not Triggering
Symptoms: # doesn't show tag suggestions
Solutions:
1. Check min_chars setting:
completion = {
min_chars = 1, -- Lower for faster triggering
},2. Ensure tags exist in vault - completion pulls from existing tags
3. Verify you're in a markdown file in the vault
Picker Issues
Picker Not Opening
Symptoms: :Obsidian search does nothing
Solutions:
1. Verify picker plugin is installed:
:Telescope " or :FzfLua, etc.2. Check picker configuration:
picker = {
name = "telescope", -- Must match installed picker
},3. Valid picker names:
"telescope"- nvim-telescope/telescope.nvim"fzf-lua"- ibhagwan/fzf-lua"mini.pick"- echasnovski/mini.pick"snacks.picker"- folke/snacks.nvim
Telescope Errors
Symptoms: Telescope throws errors when searching
Solutions:
1. Update telescope.nvim to latest version
2. Check for conflicting mappings:
:verbose map <C-x>3. Verify plenary.nvim is installed:
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-telescope/telescope.nvim",
},Link Issues
Links Not Following
Symptoms: <CR> or :Obsidian follow_link does nothing
Solutions:
1. Verify cursor is on a link:
:lua print(require("obsidian").util.cursor_on_markdown_link())2. Check link format:
- Wiki:
[[note-name]]or[[note-name|display text]] - Markdown:
[text](note-name.md)
3. Verify target note exists or allow creation:
-- In picker mappings, <C-x> creates new notes4. Check for special characters in link:
-- Links with spaces need proper encoding
[[My Note]] -- OK
[[my-note]] -- OKBacklinks Not Showing
Symptoms: :Obsidian backlinks shows empty
Solutions:
1. Ensure ripgrep can search vault:
cd ~/your-vault
rg "your-note-name" --type md2. Check note ID vs filename:
-- If using note_id_func, links use IDs not filenames3. Verify backlinks configuration:
backlinks = {
parse_headers = true,
},Image/Paste Issues
Image Paste Not Working
Symptoms: :Obsidian paste_img fails
Solutions:
1. macOS - Install pngpaste:
brew install pngpaste
which pngpaste # Verify installation2. Linux X11 - Install xclip:
sudo apt install xclip # Debian/Ubuntu
sudo pacman -S xclip # Arch3. Linux Wayland - Install wl-clipboard:
sudo apt install wl-clipboard4. Verify image is in clipboard:
# macOS
pngpaste - > /dev/null && echo "Image in clipboard"
# Linux X11
xclip -selection clipboard -t image/png -o > /dev/null 2>&1 && echo "Image in clipboard"5. Check attachments folder exists:
attachments = {
img_folder = "assets/imgs",
confirm_img_paste = true, -- Will prompt to create
},Images Not Displaying
Symptoms: Image links appear but no preview
Note: obsidian.nvim doesn't render images inline. For image preview:
- Use Obsidian app alongside
- Install image.nvim or similar
- Use snacks.nvim image feature
UI Issues
Checkboxes Not Rendering
Symptoms: - [ ] shows as plain text
Solutions:
1. Enable UI features:
ui = {
enable = true,
checkboxes = {
[" "] = { char = "", hl_group = "ObsidianTodo" },
["x"] = { char = "", hl_group = "ObsidianDone" },
},
},2. Check conceallevel:
:set conceallevel?
" Should be 1 or 2 for concealment
:set conceallevel=23. Verify font supports icons - Nerd Font required
4. Suppress conceallevel warning:
ui = {
enable = true,
ignore_conceal_warn = true,
},Highlight Groups Missing
Symptoms: No colors for tags, links, etc.
Solutions:
1. Define highlight groups:
ui = {
hl_groups = {
ObsidianTodo = { bold = true, fg = "#f78c6c" },
ObsidianDone = { bold = true, fg = "#89ddff" },
ObsidianTag = { italic = true, fg = "#89ddff" },
-- Add more as needed
},
},2. Check colorscheme compatibility - Some colorschemes override highlights
3. Apply after colorscheme loads:
vim.api.nvim_create_autocmd("ColorScheme", {
callback = function()
-- Re-apply obsidian highlights
end,
})Template Issues
Templates Not Found
Symptoms: :Obsidian template shows empty list
Solutions:
1. Verify template folder path:
templates = {
folder = "templates", -- Relative to vault root
},2. Check templates exist:
ls ~/your-vault/templates/3. Ensure templates are .md files
Template Variables Not Substituting
Symptoms: {{date}} appears literally in note
Solutions:
1. Check variable syntax: Must be {{variable}} with double braces
2. Verify substitution is defined:
templates = {
date_format = "%Y-%m-%d",
time_format = "%H:%M",
substitutions = {
-- Custom variables here
},
},3. Built-in variables:
{{title}}- Note title{{date}}- Current date{{time}}- Current time{{id}}- Note ID
Daily Notes Issues
Daily Notes in Wrong Location
Symptoms: Daily notes created in vault root
Solutions:
1. Configure daily notes folder:
daily_notes = {
folder = "daily", -- Creates ~/vault/daily/
date_format = "%Y-%m-%d",
},2. For nested folders:
daily_notes = {
folder = "journal/daily",
date_format = "%Y/%m/%Y-%m-%d", -- Creates year/month subfolders
},Yesterday/Tomorrow Skipping Wrong Days
Symptoms: Weekend handling unexpected
Solutions:
daily_notes = {
workdays_only = false, -- Include weekends
-- or
workdays_only = true, -- Skip Sat/Sun
},Performance Issues
Slow Startup
Solutions:
1. Use lazy loading:
return {
"obsidian-nvim/obsidian.nvim",
lazy = true,
ft = "markdown",
event = {
"BufReadPre " .. vim.fn.expand("~") .. "/vaults/**.md",
},
},2. Limit UI processing:
ui = {
max_file_length = 5000,
update_debounce = 200,
},Slow Completion
Solutions:
1. Increase min_chars:
completion = {
min_chars = 3,
},2. Ensure ripgrep is optimized:
# Add to .rgignore in vault
.git
.obsidian
node_modulesDebug Mode
Enable verbose logging for troubleshooting:
log_level = vim.log.levels.DEBUG,View logs:
:messages
" or check Neovim log fileGetting Help
1. GitHub Issues: https://github.com/obsidian-nvim/obsidian.nvim/issues 2. Wiki: https://github.com/obsidian-nvim/obsidian.nvim/wiki 3. Discussions: https://github.com/obsidian-nvim/obsidian.nvim/discussions
When reporting issues, include:
- Neovim version:
:version - Plugin version
- Minimal reproduction config
- Health check output:
:checkhealth obsidian