org.nvim

Capture

<prefix>c opens the template menu. :Org capture KEY selects a template directly. A count works like Emacs's prefix argument: 4<prefix>c (C-u) jumps to a template's target, 16<prefix>c (C-u C-u) to the last stored entry and 1<prefix>c (C-1) asks for the date of a date tree entry. :Org capture_here (C-0 C-c c) inserts the template at the cursor: an entry becomes a sibling of the headline there, other types go on the cursor line (before it at column 0, else after it).

The target is resolved when the capture starts, like Emacs: missing file+headline headlines and date tree nodes are created then, and the location is remembered even if the file changes (or the clock moves to another task) while you type. When the target is gone at the end (its headline was deleted), nothing is lost: the capture buffer stays open. With unnarrowed, the text is inserted into the target right away and the capture window shows the whole target file (see org-capture-unnarrowed).

Capture dates follow extend_today_until like Emacs: before that hour, %t %T %u %U and %<...> use 23:59 of the previous day and the date tree files under the previous day. A capture date without a time (the agenda day) is taken at extend_today_until o'clock; a time_prompt date keeps the current time when it is today and starts at that hour otherwise.

capture = {
  templates = {
    t = { description = "Task", template = "* TODO %?\n  %u", target = "~/org/refile.org" },
    w = "Work",   -- group label for keys starting with w
    wt = { description = "Work task", template = "* TODO %?", target = "~/org/work.org", headline = "Inbox" },
    j = { description = "Journal", template = "* %<%H:%M> %?", target = "~/org/journal.org", datetree = true },
  },
  window = "split",
}
When templates is empty (the default), Emacs's fallback is used:
t = { description = "Task", target = "", headline = "Tasks", template =
"* TODO %?\n  %u\n  %a" }, which files under "Tasks" in
default_notes_file.
Template fields:
  description        menu label
  template           string, list of lines, { file = "path" } (read from
                     that file, relative to org_directory) or a function
                     returning a string. When empty: "* %?\n  %a" for
                     entries, "- %?" for items, "- [ ] %?" for checkitems,
                     "| %? |" for table lines
  type               "entry" (default), "item", "checkitem", "table-line",
                     "plain"
  target             file (relative to org_directory, "" =
                     default_notes_file), "clock", or a function
                     returning either; defaults to default_notes_file
  headline           file+headline: under the first headline with this
                     title (TODO keyword, priority and tags ignored),
                     created at the end of the file when missing
  olp                file+olp: outline path, e.g. { "Projects", "Neovim" }
                     or "Projects/Neovim"; every node must exist
  datetree           file+olp+datetree: true or { tree_type = ... }, under
                     olp/headline when given. The date is the capture
                     date (agenda day, time_prompt, C-1) or today.
                     Existing nodes are recognized by their date
                     ("2026-09 Sept" is the September node)
  tree_type          "day" (default), "week", "month", a list of "year",
                     "quarter", "month", "week", "day", or a function(date)
                     returning { { title, compare } ... } (:tree-type)
  regexp             file+regexp: Vim regexp; the text goes where the
                     first match ends (starts, with prepend); an entry
                     becomes a child when the match is on a headline
  func / function    file+function: function(bufnr) returning the line
                     (and column) of the location, or moving the cursor
                     there; a headline line means "under this headline"
  location           the (function f) target: function() returning the
                     buffer, line and column, or nothing for the cursor
                     position in the current buffer
  id                 insert under the entry with this ID (file+id)
  target = "clock"   insert under the currently clocked task (clock)
  prepend            insert as the first child instead of the last
  empty_lines        blank lines around the text, replacing the blank
                     lines already there (also empty_lines_before /
                     empty_lines_after; items in an existing list get at
                     most one)
  table_line_pos     where a table line goes, e.g. "II-3" (3 lines before
                     the 2nd hline) or "I+1" (right after the 1st). A table
                     is created when there is none
  properties         table of properties added to the entry
  immediate_finish   finish without opening the capture window
  jump_to_captured   jump to the entry after finishing
  kill_buffer        unload the target file afterwards when the capture
                     loaded it
  refile_targets     refile.targets used when refiling from the capture
                     buffer
  clock_in           clock the entry while capturing (stops the running
                     clock); the time is logged when the capture ends
  clock_keep         with clock_in, keep the clock running afterwards
  clock_resume       with clock_in, clock the interrupted task in again
                     when the capture ends or is aborted
  time_prompt        ask for the date used by %t/%T/%u/%U and the datetree
  no_save            don't save the target file after capturing
  allow_empty        finishing with no text still saves the target (by
                     default an empty capture is refused)
  hook               function(capture_bufnr) run when the capture window
                     opens (not with immediate_finish)
  prepare_finalize   function(capture_bufnr), before the text is read
  before_finalize    function(bufnr, lnum) in the target, before saving
  after_finalize     function(bufnr, lnum) when the capture is done
  on_abort           function(target_bufnr) when the capture is aborted
                     (every hook may also be a list of functions)
  unnarrowed         edit the text in the target file itself, showing the
                     whole file (org-capture-unnarrowed)

An entry template without stars gets "* ", item templates get "- " (and continuation lines are indented under the bullet); in an existing list the item takes the list's indentation and bullet style. Items and table lines go into the first list / table of the target entry (of the file for a plain file target); "plain" text goes at the end of the entry's text, before its children (after its planning, properties and drawers with prepend), or at the end (top) of a file.

capture.templates_contexts (org-capture-templates-contexts) makes templates available only in some buffers. Each rule is { key, rules }, or { key, other_key, rules } to use other_key's template for key there (the other key is then hidden). A template is shown when any of its rules holds; rules are tables with one of in_file, not_in_file, in_mode (filetype), not_in_mode, in_buffer, not_in_buffer (Vim regexps) or functions:

capture = { templates_contexts = {
  { "m", { { in_mode = "^mail$" } } },
  { "t", "p", { { in_file = "project" } } },
} }
Template expansions:
  %?           cursor position
  %t %T        active date / date and time
  %u %U        inactive date / date and time
  %^t %^T %^u %^U  the same, asking with the calendar
  %<fmt>       strftime format, e.g. %<%Y-%m-%d>
  %a           annotation: a link to the location capture was started from
  %A           the same, asking for a description
  %l           the link without a description
  %L           the bare link target
  %i           initial content (the visual selection); the text before
               %i on its line is repeated on every line
  %x           clipboard (the * register, else +)
  %c           last yank
  %f %F        origin file name / full path
  %n           your full name (like Emacs's user-full-name)
  %k %K        the clocked task's title / a link to it
  %^{Prompt}   ask; %^{Prompt|default|opt1|opt2} offers opt1 and opt2
               and uses the default for an empty answer
  %^C %^L      ask for a clipboard value (%^L makes it a link)
  %\1 ...      the answer to the Nth %^{...} prompt
  %\*1 ...     the answer to the Nth prompt of any kind (%^t, %^g, ...)
  %^g %^G      tags prompt, completing the target file's / all agenda
               files' tags; tags on a headline are aligned
  %^{PROP}p    property prompt (offers the PROP_ALL values of the target)
  %:keyword    link information: %:link, %:description, %:annotation,
               %:initial, %:type
  %[file]      the contents of a file
  %(sexp)      an Emacs Lisp form, e.g. %(format-time-string "%Y"),
               %(upcase "%i"); or a Lua expression, %(os.getenv("HOST"))
Like Emacs, %% is not an escape: write \% for a literal "%" before an
expansion character (\%t). Leading blank lines and trailing whitespace of
the expanded text are removed.

%(sexp) is evaluated like Emacs's org-capture-expand-embedded-elisp, after the other escapes (inside it, %i, %:subject, ... are quoted for a Lisp string): a string result is inserted, nil inserts nothing and an error inserts %![Error: (void-function f)]. It runs on the Lisp interpreter of table formulas (org-table-calc, which also knows format-time-string, user-full-name, getenv, file-name-*, string-replace and replace-regexp-in-string) and, for other functions, in a separate emacs --batch with Org loaded (babel.emacs_lisp; nothing of your Emacs session exists there, and it cannot prompt: no org-read-date). A form whose first word is not a Lisp function the interpreter knows but that compiles as Lua is a Lua expression (the older org.nvim syntax); Lisp is tried when the Lua fails.

Capture window: <C-c><C-c> or :w finalizes (with a count, C-u C-c C-c, it then jumps to the entry), <C-c><C-k> aborts (unchanged text created for the target is dropped, preserving unrelated edits and generated headings with user-added content) and <C-c><C-w> finalizes and refiles the entry (entry templates only; also <prefix>w, <prefix>k, <prefix>r). The window splits the screen like Emacs (capture.window: "split", "float", "vsplit", "tab" or "current"). If saving the target fails, the capture stays open and can be retried without inserting a duplicate entry. Clock/bookmark finalization waits until the captured text is stored.

An unnarrowed capture inserts the expanded template into the target buffer when it starts, like Emacs, and opens the target buffer itself in the capture window, with the cursor at %?. The capture keys work in that window (in other windows showing the file, the file's own mappings keep working); :w just writes the file. Finalizing saves the file; aborting restores exactly the lines the capture inserted or changed (with whatever was typed into them), keeping edits elsewhere in the file, and drops headlines created for it. Closing the capture window ends the capture and leaves the text in the (unsaved) buffer. Only one unnarrowed capture can edit a file at a time.

:Org capture_goto_target  choose a template and jump to its target
                          (C-u C-c c in Emacs)
:Org capture_goto_last    jump to the last captured (or refiled) entry
                          (C-u C-u C-c c)
:Org capture_string       ask for a string and capture it as the initial
                          text %i (org-capture-string)
:Org bookmark_jump        jump to a bookmark: capture and refile save
                          the last location as the bookmarks named in
                          bookmark_names (org-bookmark-names-plist:
                          "org-capture-last-stored",
                          "org-refile-last-stored"), in
                          stdpath("data")/org/bookmarks.json, so the
                          jumps above also work in a later session
From the agenda, K captures with the date at point as the default date
(with a count of 1, C-1, at the time of the item at point or now). With
capture.use_agenda_date, the global capture key does the same there.
Besides the templates' prepare_finalize, before_finalize and
after_finalize functions, every capture fires User autocmds, after the
template's function, like Emacs's global hooks:
  OrgCapturePrepareFinalize  capture buffer still open; data { buf }
                             ({ immediate = true } for immediate_finish
                             templates, which have no capture buffer)
                             (org-capture-prepare-finalize-hook)
  OrgCaptureBeforeFinalize   entry stored, target not yet saved; data
                             { bufnr, line }
                             (org-capture-before-finalize-hook)
  OrgCaptureAfterFinalize    capture done; data { bufnr, line }
                             (org-capture-after-finalize-hook)
Aborting (<C-c><C-k>) fires the prepare and after events with
aborted = true, but not the before event, like org-capture-kill.
vim.api.nvim_create_autocmd("User", {
  pattern = "OrgCaptureAfterFinalize",
  callback = function(ev)
    if not ev.data.aborted then vim.notify("captured") end
  end,
})
org-protocol
org-protocol:// URLs from a browser or another program capture, store
links and open files in a running Neovim, like Emacs org-protocol.el:
  org-protocol://capture?template=KEY&url=URL&title=TITLE&body=TEXT
      capture with template KEY (protocol.default_template_key, else
      choose); %:link, %:description, %a, %i (body) and
      %:<param> come from the URL
  org-protocol://store-link?url=URL&title=TITLE
      store the link for org-insert-link and yank the URL
  org-protocol://open-source?url=URL
      open the local file behind a published URL (protocol.projects,
      like org-protocol-project-alist: base_url, working_directory,
      online_suffix, working_suffix, rewrites)
Old-style URLs (capture://KEY/URL/TITLE/BODY) work too, their data
split on protocol.data_separator (a Vim regex, /+ or ? by default;
org-protocol-data-separator), and protocol.handlers adds sub-protocols.
Call it from Lua with require("org.protocol").handle(url) or
:Org protocol <url>.
:Org protocol_create    ask for a base URL, working directory and suffixes
                        and add a protocol.projects entry
                        (org-protocol-create). It lasts for the session;
                        the Lua to add to your setup() is shown (Emacs
                        saves it to the custom file).
:Org protocol_create_for_org
                        the same with the defaults of the publishing
                        project of the current file (org-protocol-create-for-org)

Neovim must listen on a socket (nvim --listen ~/.cache/nvim/org.sock, or v:servername) and the OS must send org-protocol: URLs to a handler that forwards them, e.g. on Linux a desktop file with MimeType=x-scheme-handler/org-protocol; and:

Exec=nvim --server ~/.cache/nvim/org.sock --remote-expr "v:lua.require'org.protocol'.handle('%u')"

then xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol. On macOS register the scheme with a small app bundle (an AppleScript on open location handler running the same command). A bookmarklet:

javascript:location.href='org-protocol://capture?'+new URLSearchParams({
  template:'p', url:location.href, title:document.title,
  body:window.getSelection()});void(0)

Emacs's emacsclient integration (server-visit-files advice, :kill-client) has no counterpart: the handler command above plays that role.

Plain links through tags files
Like Emacs org-ctags, <<targets>> in Org files can be tags: with
ctags.enabled = true (Emacs: org-ctags-enable), a plain link [[foo]]
(also [[#id]], [[*heading]], [[(ref)]]) first goes through
ctags.open_link_functions, tried in order until one handles it:
  "find_tag"            jump to the tag with :tag (CTRL-T goes back)
  "ask_rebuild_tags_file_then_find_tag"   ask, rebuild the tags file,
                        then look again (also without asking:
                        "rebuild_tags_file_then_find_tag")
  "ask_append_topic"    ask, then append a new topic * <<Foo>> to the
                        buffer (ctags.new_topic_template, %t = the
                        capitalized name; "append_topic" does not ask)
  "ask_visit_buffer_or_file" / "visit_buffer_or_file"   visit NAME.org
                        (asking to create it)
  "fail_silently"       stop here (no search in the buffer)
or functions of the link text. When none handles it, the link is searched
in the buffer as usual. Tags come from Neovim's 'tags' option (by default
the tags file next to the file or above it); Emacs uses TAGS files.
:Org ctags_create_tags  run ctags.path_to_ctags (Exuberant or Universal
                        ctags) on the file's directory, writing its tags
                        file with the Org targets (ctags.tag_regexp)
:Org ctags_find_tag_interactive   choose a tag (completed) and jump to
                        it; an unknown one goes through the functions
                        above (org-ctags-find-tag-interactive)
:Org ctags_find_tag, ctags_get_filename_for_tag, ctags_all_tags,
ctags_open_file, ctags_visit_buffer_or_file, ctags_append_topic
                        the other org-ctags commands, asking for their
                        argument

RSS and Atom feeds Items of RSS 2.0 and Atom feeds are added as children of an inbox headline (org-feed.el). List the feeds in feed.feeds (org-feed-alist):

feed = {
  feeds = {
    { name = "Slashdot", url = "https://rss.slashdot.org/Slashdot/slashdot",
      file = "~/org/feeds.org", headline = "Slashdot Entries" },
    -- the positional Emacs form works too
    { "Local", "file:///home/me/feed.xml", "~/org/feeds.org", "Local" },
  },
}

file defaults to the current buffer's file; the headline (any level, tags allowed) is created at the end of the file when missing. Each update adds the items not seen before, one level below the inbox and after its last child, and records every item's GUID, a "handled" flag and a SHA-1 of the item in the :FEEDSTATUS: drawer of the inbox, as the Lisp list Emacs writes, so Emacs and Neovim can update the same file. Items removed from the feed are dropped from the drawer.

Per-feed options (Emacs keyword in parentheses):
  template         item template, default feed.default_template (:template)
  formatter        function(entry) returning the Org text of the item,
                   instead of the template (:formatter)
  filter           function(entry) returning the entry (possibly modified)
                   or nil to skip it; skipped items stay unhandled (:filter)
  new_handler      function(entries, ctx) called with the new items instead
                   of adding them; the cursor is on the inbox headline and
                   ctx is { bufnr, lnum, feed } (:new-handler)
  changed_handler  function(entries, ctx) called with handled items whose
                   text changed (:changed-handler)
  parse_feed       "rss", "atom" or function(text) returning entries with
                   guid and item_full_text (:parse-feed)
  parse_entry      "rss", "atom" or function(entry) adding the fields
                   (:parse-entry)
  drawer           status drawer, default feed.drawer (:drawer)
  retrieve_method  like feed.retrieve_method, for this feed
Without parse_feed, a feed with a <feed> root and no <item> is read
as Atom, anything else as RSS. An entry is a table: RSS items have a field
per <tag>value</tag> (title, link, description, pubDate, ...,
entities decoded and CDATA unwrapped) and guid_permalink; Atom entries
have title, link (the first <link href>), description (from
<content>, else <summary>; xhtml content as markup), id, updated,
published and summary. Both have guid, item_full_text and
handled.
Template escapes (org-feed-default-template, "\n* %h\n  %U\n  %description\n  %a\n"):
  %h       the title, else the first line of the description
  %t %T    date, date and time, from pubDate (else now), active
  %u %U    the same, inactive
  %a       [[link]] from the guid when it is a permalink, else link
  %name    any field of the entry: %title, %description, %pubDate...;
           alone on its line, its later lines get the same indentation
  %(expr)  a Lua expression (the entry is entry), after the escapes
           above; \% keeps a literal %

Options: feed.default_template, feed.drawer ("FEEDSTATUS"), feed.save_after_adding (true) and feed.retrieve_method ("curl", "wget" or a function(url) returning the text; file:// URLs and local paths are read directly). User autocmds OrgFeedBeforeAdding and OrgFeedAfterAdding (org-feed-before/after-adding-hook) fire around the changes with data = { feed, file, bufnr, count? }.

<C-c><C-x>g  :Org feed_update_all     update every feed (org-feed-update-all)
             :Org feed_update [name]  update one feed (org-feed-update)
<C-c><C-x>G  :Org feed_goto_inbox [name]  go to a feed's inbox
             :Org feed_show_raw [name]    show the feed's XML
Without a name these prompt (goto_inbox and show_raw take the only feed
when there is one). Retrieval is synchronous, like Emacs.