A Neovim port of the obsidian-tasks plugin. Render, filter, and edit tasks across your Obsidian vault β directly in Neovim.
Status: v1 in development. See v1 Features & Limitations.
- Neovim β₯ 0.10
- ripgrep (
rg) on yourPATHβ used to scan the vault
That's it: obsidian-tasks has no required Neovim-plugin dependencies. It detects
your vault by walking up to the nearest .obsidian/ directory and scans it with
ripgrep directly. Run :checkhealth obsidian-tasks to confirm your setup.
- obsidian.nvim β when present, its
checkbox-symbol cycle (
Obsidian.opts.checkbox.order) is bridged so symbols it cycles through are accepted as valid statuses rather than reverted. - blink.cmp β enables task-field completion (see blink.cmp Registration).
{
"snapwich/obsidian-tasks.nvim",
config = function()
require("obsidian-tasks").setup({})
end,
}ripgrep is a system requirement, not a plugin β install it via your package manager
(brew install ripgrep, apt install ripgrep, β¦). Add obsidian.nvim / blink.cmp
to dependencies only if you want those optional integrations.
Call setup() once during Neovim startup (e.g. inside the lazy.nvim config function).
require("obsidian-tasks").setup({})All options are optional β the empty call above gives you sensible defaults.
require("obsidian-tasks").setup({
-- Only parse/render tasks whose description contains this string.
-- nil means no filter (show all tasks).
global_filter = nil, -- string | nil
-- Automatically render task extmarks when a vault markdown buffer is opened.
auto_render = true, -- boolean
-- Fold each ```tasks block by default when a dashboard file is opened.
-- The foldtext shows a query summary and result count, e.g. "π not done (3)".
-- Press `i` (or `zo`) to expand a fold; press `zc` to collapse it again.
default_folded = true, -- boolean
-- Install buffer-local leader keymaps (<leader>tt, <leader>te, etc.) on
-- every markdown buffer in a vault workspace. Set false to manage keymaps
-- yourself.
setup_keymaps = true, -- boolean
-- strftime pattern used when stamping the done date (β
) on task completion.
done_date_format = "%Y-%m-%d", -- string (must contain %)
-- Timezone used when stamping the done date.
-- "local" uses the system timezone; "utc" forces UTC (prepends "!" to the format).
done_date_tz = "local", -- "local" | "utc"
-- Default file for :ObsidianTask new when no heading context is found.
capture_file = nil, -- string | nil (absolute or vault-relative path)
-- Override the default status cycle (Todo β InProgress β Done β Cancelled β OnHold).
-- Each entry: { char = "x", name = "Done", next = "todo" }
statuses = nil, -- table | nil
-- Hide the `tasks:` / `filter:` metadata lines in rendered query blocks.
hide_query_metadata = false, -- boolean
-- Keep a task visible (dimmed) after a mutation drops it from the live
-- filter set β e.g. completing a task in a `not done` query. Cleared by
-- :ObsidianTask refresh / <leader>tr, by buffer reload, or when the task
-- re-enters the filter. Set false for obsidian-tasks-style instant vanish.
linger_on_filter_exit = true, -- boolean
-- Highlight group applied to lingered rows (and, with dim_completed_tasks,
-- live Done/Cancelled rows). Linked to `Comment` by default; override the
-- link in your colorscheme or point this at a different group.
linger_hl_group = "ObsidianTasksLinger", -- string
-- Sink live Done / Cancelled tasks to the bottom of each group and apply
-- linger_hl_group to them. User sort is preserved within each tier
-- (non-completed first, completed below).
dim_completed_tasks = true, -- boolean
-- blink.cmp source integration.
blink_cmp = {
enabled = true, -- boolean: set false to disable the cmp source entirely
},
-- Natural-language date input in the cmp source.
date_input = {
natural_language = true, -- boolean: enable NL parsing (e.g. "next monday")
-- Suggestion phrases shown in the date-field dropdown.
-- Override to shorten or localise the list.
suggestions = { -- string[]
"today",
"tomorrow",
"next monday",
"next week",
"in 3 days",
},
},
-- Minimum log level: "debug" | "info" | "warn" | "error"
log_level = "info", -- string
-- Files larger than this (bytes) are skipped by the vault scanner.
max_file_bytes = 1048576, -- positive integer (default 1 MiB)
-- DateFallback: when true, tasks in files whose basename contains a
-- YYYY-MM-DD date inherit it as their scheduled date (only when the
-- task has no own β³). Mirrors upstream's `useFilenameAsScheduledDate`.
use_filename_as_scheduled_date = false, -- boolean
})Dashboard files contain ```tasks query blocks. When a dashboard buffer is opened,
obsidian-tasks renders matching tasks as real buffer text below each fence and wraps
each block in a manual fold so the dashboard stays compact.
With default_folded = true (the default), every query block opens with its fence lines
collapsed into a one-line summary; rendered task lines remain visible below:
π not done (3)
- [ ] Task A π
2026-05-12 [[note-a]]
- [ ] Task B π
2026-05-13 [[note-b]]
- [ ] Task C π
2026-05-14 [[note-c]]
The foldtext shows a summary of the query filter and the result count. Expand the fold to edit the underlying query:
| Key | Action |
|---|---|
i |
Open fold and enter insert mode on query |
zo / zO |
Open fold (stay in normal mode) |
zc |
Close fold |
zR |
Open all folds in the buffer |
zM |
Close all folds in the buffer |
Rendered task lines below the fence are managed by the plugin, but you can edit them directly β the changes propagate back to the source file:
- Insert-mode edits. Change a task's description or a field value while in
insert mode; on
InsertLeavethe edit is flushed to the source file. Natural-language date values (e.g.π next monday) are normalized to ISO dates during the flush. - Inserting a task. Add a new line inside a rendered region to create a new task; group-by attributes for the region are auto-added so the row stays in its group.
- Deleting a task. Delete a rendered row to remove the corresponding task line from the source file.
Status character. In normal mode, typing a configured status symbol over the
character between [ and ] commits the change to the source (same end-state as
<leader>tt). For example, r x over the space in - [ ] task writes
- [x] task. The symbol must be in your configured statuses set; unknown chars
revert.
Edits the plugin can't classify β normal-mode tampering with managed text, or an
unrecognized change β are reverted on the next event-loop tick. The rendered
region snaps back to its canonical state. You can also use the leader keymaps to
mutate a task, press <CR> with the cursor on the trailing [[wikilink]] to
follow it (handled by obsidian.nvim), or <leader>tg to jump to the source.
Undo. u and <C-r> are overridden on dashboard buffers: they walk a
per-dashboard undo/redo ring that reverses the source-file mutation (not just the
visual line). When the ring is empty they fall back to native :undo / :redo,
so prose edits still undo normally.
Saving a dashboard buffer (:w) writes only the source content β query blocks and
prose β to disk. Rendered task lines are never written to the file. Reopening the file
produces the same visual state.
Two visual options control how completed and "just-changed" tasks appear in a query:
dim_completed_tasks(defaulttrue) β live Done/Cancelled tasks are sunk to the bottom of their group and rendered withlinger_hl_group. Yoursortorder is preserved within each tier.linger_on_filter_exit(defaulttrue) β when a mutation (e.g.<leader>ttto toggle done) would cause a task to drop out of the query's filter, the row stays visible but dimmed instead of vanishing. Lingered rows clear on:ObsidianTask refresh/<leader>tr, on buffer reload, or when the task re-enters the filter.
Both effects use the ObsidianTasksLinger highlight group, linked to Comment by
default. Override with e.g.:
vim.api.nvim_set_hl(0, "ObsidianTasksLinger", { fg = "#666666", italic = true })Set either option to false for obsidian-tasks-desktop parity (immediate vanish, no dim).
All keymaps below are auto-installed when setup_keymaps = true (the default).
Set setup_keymaps = false in your setup() call to opt out and wire your own.
Installed on every markdown buffer in a vault workspace β both rendered dashboard rows and raw source task lines. The handlers resolve the task under the cursor either way (managed extmark first, then a source-line parse):
| Keymap | Action |
|---|---|
<leader>tt |
Toggle done/not-done |
<leader>te |
Edit task description (vim.ui.input prompt) |
<leader>tp |
Cycle priority (none β highest β β¦ β lowest β none) |
<leader>td |
Set/edit due date (YYYY-MM-DD prompt) |
<leader>tT |
Edit tags (comma-separated prompt) |
<leader>tg |
Jump to source from anywhere on a task row |
<leader>tD |
Delete task (confirm prompt, removes source line) |
Installed only on rendered dashboard buffers (first draw):
| Keymap | Action |
|---|---|
<leader>tr |
Force re-render all query regions in this buffer |
u |
Undo last dashboard edit (plugin ring β native) |
<C-r> |
Redo last dashboard edit (plugin ring β native) |
Note on <CR> and gf: the plugin does not override these. When obsidian.nvim
is installed, its ftplugin re-registers its smart_action <CR> after each render,
racing any buffer-local handler we'd install β press <CR> with the cursor on the
trailing [[wikilink]] of a rendered task and obsidian.nvim will follow it. Either way,
<leader>tg jumps to the source from any column on the row. (Without obsidian.nvim,
<CR> / gf keep their native behavior.)
| Command | Description |
|---|---|
:ObsidianTask toggle |
Cycle the status of the task under the cursor |
:ObsidianTask done |
Mark task done and stamp done date (honors π delete to remove the line) |
:ObsidianTask cancel |
Mark task cancelled |
:ObsidianTask inProgress |
Set status to In Progress ([/]) |
:ObsidianTask onHold |
Set status to On Hold ([h]) |
:ObsidianTask due [DATE] |
Set / edit the due date (π ) |
:ObsidianTask scheduled |
Set / edit the scheduled date (β³) |
:ObsidianTask start [DATE] |
Set / edit the start date (π«) |
:ObsidianTask priority |
Cycle / set priority |
:ObsidianTask recurrence |
Set the recurrence pattern (π) |
:ObsidianTask tags |
Edit the task's tags |
:ObsidianTask postpone [N] |
Bump primary date by N days (default +1; due > scheduled > start priority) |
:ObsidianTask id [VAL] |
Generate (or set) a 6-char base36 task id for use with depends on filters |
:ObsidianTask new |
Create a new task (appends to capture_file or current file) |
:ObsidianTask refresh |
Clear and re-render all query blocks in the current buffer (clears lingers) |
:ObsidianTask render |
Render query blocks in the current buffer (for auto_render = false) |
:ObsidianTask goto |
Jump to the source line of a rendered task |
obsidian-tasks ships an optional blink.cmp source that provides field-icon completions, per-field value suggestions (dates, recurrence patterns, β¦), and natural-language date parsing on every task line. It is only active if you install blink.cmp and register the source; the plugin never requires it.
Register the source in your blink.cmp config:
require("blink.cmp").setup({
sources = {
default = { "lsp", "path", "snippets", "buffer", "obsidian-tasks" },
providers = {
["obsidian-tasks"] = {
module = "obsidian-tasks.cmp.source",
name = "ObsidianTasks",
},
},
},
})The source activates automatically on task lines (- [ ] β¦) inside any Obsidian vault
(a folder containing a .obsidian/ directory). No additional configuration is required
beyond require("obsidian-tasks").setup({}).
To disable the source without removing it from blink, set blink_cmp = { enabled = false } in
your setup() call.
What works in v1:
- Emoji-field and Dataview-field task parsing and serialization
- Real-text task rendering in dashboard buffers with manual fold per query block
- BufWriteCmd save handler β only source content (queries + prose) written to disk
- In-memory vault index (in-Neovim edits propagate on save; external edits re-indexed automatically when Neovim regains focus, or on demand via
:ObsidianTask refresh) - Query syntax portable with upstream obsidian-tasks: bare-infix AND/OR/NOT, case-insensitive operators, optional parens (
A AND B AND Cand(A) AND (B)both work).path/folder/rootmatch against the vault-relative path (e.g.daily/2024-03-15.md, not the absolute filesystem path) so query results match Obsidian's behaviour when the same vault is opened in both - Query filters by field:
done/not done,status.name,status.type,priority is/above/below, date filters withbefore/after/on/on or before/on or after/in(+ two-date ranges and year / month / ISO-week / quarter period shortcuts),tag includes/does not include,path/folder/root/filename/backlink/description/heading(with regex),is recurring,recurrence includes,urgency above/below,exclude sub-items,random - Dependency filters:
id is <X>,depends on <X>,is blocking,is blocked(with their negated forms) sort byandgroup byfor all the above keys, includingrandomlimit N(per group + total cap),hide <subkey>for 14 metadata subkeysexplainkeyword renders the parsed query summary in the result- Urgency: full upstream-parity formula across due / scheduled / start / priority
- Status cycling with customizable statuses;
donefilter aligned with upstream (CANCELLED counts as done) - OnCompletion π:
π deleteremoves the line when the task is marked done/toggled to a completed status - DateFallback: opt-in (
use_filename_as_scheduled_date) inheritance ofYYYY-MM-DDfilenames as the task's scheduled date - Dim completed tasks and linger rows that drop out of the filter after a mutation
- Leader keymaps for task mutation on any task line β dashboard rows and raw source files
- Edit-in-place: insert-mode description/field edits, task inserts, and deletes flush to source on
InsertLeave; per-dashboard undo/redo ring - blink.cmp source: field-icon completion, value suggestions, NL date parsing (with number-word forms:
in two weeks) :ObsidianTaskcommand suite (toggle / done / cancel / inProgress / onHold / due / scheduled / start / priority / recurrence / tags / postpone [N] / id / refresh / render / new / goto)
v1 limitations:
- Recurrence (π): parsed and preserved as opaque text;
next_occurrencecomputation is a v2 feature and raises an error if called directly. - Language: English-only NL date phrases.
- External vault edits: re-indexed automatically when Neovim regains focus, but only for
files already in the index β files created externally while Neovim is running are not
picked up until opened or until a full
:ObsidianTask refresh. - JS expression filters (
where filter by function) are intentionally not supported β the line emits a structuredunsupportederror in the result.
