README.md
August 3, 2026 · View on GitHub
A keyboard-first task cockpit for one or many Obsidian vaults.
Your Markdown stays the source of truth.
Install · Configure · Use · Roadmap
Why obsidian-tasks.nvim?
Keep notes in Obsidian and stay in Neovim when it is time to execute. The plugin reads ordinary Markdown checkboxes, builds an ordered tag tree, and lets you complete or create tasks without introducing a database or rewriting your vault.
| Capability | |
|---|---|
| 🗂️ | Combine multiple vaults or collect many Markdown files into one logical repository |
| 🌳 | Group tasks recursively by tag order: #work #frontend #urgent |
| ⚡ | Complete tasks with <Space> and append ✅ YYYY-MM-DD automatically |
| ✍️ | Create tasks through a guided repository, tag, start date, and deadline flow |
| 🛠️ | Edit, delete, and undo task changes directly from the task view |
| 🔎 | Filter by tag, sort by source/title/deadline, and collapse native tag folds |
| 🪟 | Use a centered floating window or a regular native split |
| 🔒 | Detect stale source lines before writing and update files atomically |
| 🧩 | Run on the built-in Neovim API with no required dependencies |
Installation
lazy.nvim
{
"jjuchara/obsidian-tasks.nvim",
opts = {
repositories = {
{
name = "personal",
alias = "Personal tasks",
path = "~/notes/personal/Tasks.md",
},
},
mappings = {
open = "<leader>to",
create = "<leader>ta",
},
},
}
Native packages
git clone https://github.com/jjuchara/obsidian-tasks.nvim \
~/.local/share/nvim/site/pack/plugins/start/obsidian-tasks.nvim
Then call setup() from your configuration.
Configuration
The only required option is repositories. A repository accepts either a direct path to a task file or a vault plus a relative todo_file. Its optional alias is used as the display label while name remains the repository identifier.
require("obsidian-tasks").setup({
repositories = {
{
name = "personal",
alias = "Personal tasks", -- optional display label
vault = "~/notes/personal",
todo_file = "Tasks.md", -- writable target for new tasks
sources = { -- optional read/write task sources
{ glob = "Projects/**/*.md", tags = { "#project" } },
{ glob = "Areas/**/*.md", tags = { "#area" } },
},
},
{
name = "work",
vault = "~/notes/work",
todo_file = "Projects/Tasks.md",
},
},
view = {
type = "float", -- "float" or "window"
width = 0.5, -- fraction of editor width
height = 0.5, -- fraction of editor height
border = "rounded",
title = " Obsidian tasks ",
close_on_leave = true, -- close a float when focus leaves it
repository_mode = "sections", -- "sections" or "tabs"
window_command = "botright new",
status = "active", -- "active", "done", or "all"
sort = "source", -- "source", "deadline", or "title"
filter = nil, -- optional initial tag filter, e.g. "#work"
fold_level = 99, -- initially expand groups through this tag depth
},
dates = {
display_format = "%d.%m.%Y", -- strftime format used in the task view
picker = "calendar", -- "calendar" by default, or "input"
},
creation = {
default_start_today = true,
prompt_additional_tags = true,
infinity_marker = "♾️",
},
completion = {
marker = "✅",
},
mappings = {
open = "<leader>to", -- global mapping, nil disables it
create = "<leader>ta", -- global mapping, nil disables it
create_in_view = "a",
toggle = "<Space>",
edit = "<CR>",
delete = "d",
undo = "u",
open_source = "gf",
refresh = "r",
close = { "q", "<Esc>" },
cycle_status = "s",
cycle_sort = "o",
filter = "f",
next_repository = "<Tab>",
previous_repository = "<S-Tab>",
},
})
Each repository has one writable path or vault + todo_file. New tasks are appended there. Optional sources collect tasks from additional Markdown files without copying them into the writable task file. A source glob may be absolute or relative to vault; its tags are view-only prefixes used for grouping and filtering.
Project collections can derive one additional view-only tag from the frontmatter of the matching project metadata note:
sources = {
{
glob = "1. Projects/**/*.md",
tags = { "#project" },
project_tag = {
root = "1. Projects",
marker = "#projects",
exclude = { "#projects", "#project-support" },
},
},
}
For a task below 1. Projects/example/, the collector looks for a Markdown file in that project carrying the #projects frontmatter marker and uses its first non-excluded tag. A physical #planning task can therefore render under #project → #example → #planning. Computed tags are never written to the source task during editing; creation pickers also expose only physical Markdown tags.
Usage
Commands
| Command | Action |
|---|---|
:ObsidianTasks | Open the task view |
:ObsidianTasksCreate | Start the guided creation flow |
:ObsidianTasksRefresh | Reload every open task view |
:ObsidianTasksSort [source|deadline|title] | Set sorting, or cycle it without an argument |
:ObsidianTasksFilter [#tag|clear] | Select or set a tag filter, or clear it |
Task view
| Key | Action |
|---|---|
a | Create a task without leaving the task view |
<Space> | Toggle the task under the cursor |
<CR> | Edit task text, tags, start date, and deadline |
d | Delete the task under the cursor after confirmation |
u | Undo the latest task operation |
gf | Open the Markdown source at the task line |
s | Cycle active → done → all |
o | Cycle source → deadline → title sorting |
f | Select a tag filter or clear the active filter |
za, zc, zo | Toggle, close, or open the tag group under the cursor |
r | Refresh from disk |
q, <Esc> | Close the view |
A floating view is modal by default: clicking another window closes it. Set view.close_on_leave = false if you prefer a persistent float.
Task-view mappings must be unique. For example, do not assign both edit and open_source to <CR>.
Tag filtering matches any tag on a task, remains active after refresh, and applies to every open task view.
Tag groups use native Neovim folds and expose a fold column for mouse interaction. Their expanded and collapsed states are preserved across refreshes, sorting, filtering, and repository navigation. Set view.fold_level = 0 to start with top-level groups collapsed, or use a larger depth to reveal initial levels.
Creating a task
- Select a repository when more than one is configured.
- Enter the task text.
- Select or create the primary tag, press Space to choose the tag under the cursor, or press Enter immediately to continue without one.
- Choose any number of additional tags from the existing-tag list. Press Enter to continue, or press Space to toggle the tag under the cursor. Selected additional tags are marked with
[x]; use+ new tag...to add and select a new tag. - Confirm the start date and optional deadline. Enter
yesterday,today,tomorrow, an ISO date such as2026-07-10, or a date matchingdates.display_format. Leading zeroes are optional, so7.3.2026is accepted for%d.%m.%Y.
The default dates.picker = "calendar" opens the built-in dependency-free calendar for every start and due date in creation and editing. Use h/l to move by day, j/k by week, H/L by month, t for today, <CR> to select, x to clear, and q/<Esc> to cancel. Press i or <C-i> for native manual input; <C-i> there returns to the same calendar. Set dates.picker = "input" explicitly to retain the prompt-first workflow. Cancelling neither clears the date nor persists a partially completed task mutation.
The completion notification shows the persisted tag path, for example #work → #gantt.
When task creation starts from an open task view, the view stays open and the newly created task is focused after refresh.
Dates are always stored as YYYY-MM-DD. dates.display_format changes rendered dates and the examples shown in creation prompts without modifying Markdown. Active tasks due today or tomorrow use ObsidianTasksDueSoon; overdue tasks use ObsidianTasksOverdue.
Deadline sorting adds virtual #overdue, #due-soon, and #on-track groups. #due-soon covers today and tomorrow; later deadlines, completed tasks, and tasks without deadlines are #on-track.
Integrations may call require("obsidian-tasks").create(options). repository selects one
configured repository by name and bypasses the active task view or repository picker;
initial_name pre-fills the editable task prompt; and description_suffix adds caller-owned
context, such as an Obsidian wikilink, before tags and task metadata. All fields are optional, so
create() retains the normal interactive behavior. An unknown explicit repository fails before
any task mutation.
Integrations may also subscribe to successful task-view checkbox writes:
local unsubscribe = require("obsidian-tasks").on_toggle(function(event)
if event.done then
vim.notify("completed: " .. event.task.text)
end
end)
The event contains the original parsed task, the resulting boolean done, and persisted path,
lnum, and raw fields. on_toggle() returns an idempotent unsubscribe function. Listener errors
are notified after persistence and do not roll back or misreport the task mutation.
Single-line HTML comments can carry durable integration markers: the task view and edit prompt hide
them, while a successful task edit preserves them in the Markdown source.
Editing a task from the task view rewrites the Markdown task line while preserving the checkbox state, creation date, and completion date. Clearing the deadline stores the configured no-deadline marker.
Markdown format
## #work
- [ ] Ship the release #work #frontend #urgent 📅 2026-07-10
- [x] Fix the regression #work #backend ✅ 2026-07-02
Tags are significant from left to right:
#work
└── #frontend
└── #urgent
└── Ship the release
When a task has no inline tag, the nearest ## #tag heading is used as a fallback. YAML frontmatter and fenced code blocks are ignored.
Collected sources
Collected tasks keep the path and line number of their original Markdown file. Toggle, edit, delete, undo, and gf therefore operate on the real source instead of a rendered or copied query result. This provides Dataview-style aggregation without executing Dataview or depending on a running Obsidian process.
Source globs are evaluated in configuration order. If several globs match the same file, the first match owns it so the task is shown only once.
Multiple repositories
repository_mode = "sections" renders all repositories in one buffer with repository headers. repository_mode = "tabs" shows repository tabs inside the task view and renders tasks from the active repository. Use <Tab> / <S-Tab> or click a tab to switch repositories.
Development
Run the test suite without a plugin manager:
nvim --headless -u NONE -i NONE \
"+set rtp+=$PWD" \
"+luafile tests/run.lua" \
+qa
See CONTRIBUTING.md for the contribution workflow, DECISIONS.md for durable product and architecture choices, and :help obsidian-tasks for the built-in manual.
Status
The core workflow is usable today. Advanced queries, recurrence, and live filesystem watching are tracked in FUTURE.md.
License
MIT © 2026 jjuchara