Skip to content

Org Mode

nvim-orgmode is a Orgmode clone written in Lua for Neovim. Org-mode is a flexible note-taking system that was originally created for Emacs. It has gained wide-spread acclaim and was eventually ported to Neovim. This page is heavily focused to the nvim plugin, but you can follow the concepts for emacs as well.

If you use Android try orgzly.


Add to your plugin config:

use {'nvim-orgmode/orgmode', config = function()

Then install it with :PackerInstall.

Tweak the configuration:

-- init.lua

-- Load custom treesitter grammar for org filetype

-- Treesitter configuration
require('nvim-treesitter.configs').setup {
  -- If TS highlights are not enabled at all, or disabled via `disable` prop,
  -- highlighting will fallback to default Vim syntax highlighting
  highlight = {
    enable = true,
    -- Required for spellcheck, some LaTex highlights and
    -- code block highlights that do not have ts grammar
    additional_vim_regex_highlighting = {'org'},
  ensure_installed = {'org'}, -- Or run :TSUpdate org

  org_agenda_files = {'~/Dropbox/org/*', '~/my-orgs/**/*'},
  org_default_notes_file = '~/Dropbox/org/',

You can check the default configuration file here.

Key bindings

Mappings or Key bindings can be changed on the mappings attribute of the setup. The program has these kinds of mappings:

For example the global mappings live under and can be overridden like this:

  mappings = {
    global = {
      org_agenda = 'gA',
      org_capture = 'gC'

Be ready when breaking changes come

The developers have created an issue to track breaking changes, subscribe to it so you're notified in advance.


If you are new to Orgmode, check the vim Dotoo video, it's another plugin but the developers say it's the same. If you, like me, prefer written tutorials check the hands-on tutorial.

If you get lost in any view you can use g? to get the mappings of the view.

The Org file syntax


Any line starting with one or more asterisks * but without any preceding whitespace is a heading (also called headline).

* Org Bullets
* Vim table-mode

Once you are over a header, you can create a new header at the same level below the current subtree <leader><enter>, if you want to add a heading after the current item use <control><enter> if you use these key bindings:

  mappings = {
    org = {
      org_meta_return = '<c-cr>',
      org_insert_heading_respect_content = '<leader><cr>',

To be able to use it in insert mode too add below the org-mode configuration:

  imap <c-cr> <c-o><c-cr>
  imap <leader><cr> <c-o><leader><cr>

The number of asterisks denotes the level of the heading: the more asterisks, the deeper the level. That is how we achieve nested structures in our org files.

* Org Bullets
** Synopsis
* Vim table-mode

The content within a heading can be free form text, include links, be a list, or any combination thereof. For example:

* Org Bullets
** Synopsis
   This plugin is a clone of org-bullets. It replaces the asterisks in org
   syntax with unicode characters.
* Vim table-mode

The full syntax for a headline is

*     TODO    [#A]     foo   :bar:baz:


  • KEYWORD: if present, turns the heading into a TODO item.
  • PRIORITY sets a priority level to be used in the Agenda.
  • TITLE is the main body of the heading.
  • TAGS is a colon surrounded and delimited list of tags used in searching in the Agenda.
Toogle line to headline

You can change a text line into a headline with <leader>h (Default: <leader>o*) with the next configuration:

org = {
  org_toggle_heading = '<leader>h',

If you have a checkbox inside a TODO item, it will transform it to a children TODO item.

Change heading level

To change the heading level use << or >>. It doesn't work in visual mode though, if you want to change the level of the whole subtree you can use <S and >S.

org = {
  org_do_promote = '<<',
  org_do_demote = '>>',
  org_promote_subtree = '<S',
  org_demote_subtree = '>S',

To be able to use it in insert mode too add below the org-mode configuration:

  imap >> <esc>>>
  imap << <esc><<
  imap >S <esc>>s
  imap <S <esc><s


If you don't like seeing so many stars, you can enable the org_hide_leading_stars = true option. To me it looks much cleaner.

Moving headings

To move the subtrees up and down you can use J (Default <leader>oJ) and K (Default <leader>oK) with the next conf:

    org = {
      org_move_subtree_up = "K",
      org_move_subtree_down = "J",
Folding headings

To fold the headings you can use either the normal vim bindings zc, zo, zM, ... or <tab> to toogle the fold of an element or <shift><tab> to toogle the whole file.

It's easy to navigate through your heading tree with:

  • Next/previous heading of any level with <control>j/<control>k (Default }/{)
  • Next/previous heading of the same level with <control>n/<control>p (Default ]]/[[)
  • Go to the parent heading with gp (Default g{)
org = {
  org_next_visible_heading = '<c-j>',
  org_previous_visible_heading = '<c-k>',
  org_forward_heading_same_level = '<c-n>',
  org_backward_heading_same_level = '<c-p>',
  outline_up_heading = 'gp',

To be able to use it in insert mode too add below the org-mode configuration:

  imap <c-j> <esc><c-j>
  imap <c-k> <esc><c-k>
  imap <c-n> <esc><c-n>
  imap <c-p> <esc><c-p>

TODO items

TODO items are meant to model tasks that evolve between states.

As creating TODO items is quite common you can:

  • Create an item with the same level as the item above in the current position with ;t (by default is <leader>oit).
  • Create an item with the same level as the item above after all the children of the item above with ;T (by default is <leader>oit).
org = {
  org_insert_todo_heading = ";t",
  org_insert_todo_heading_respect_content = ";T",

To be able to use it in insert mode too add below the org-mode configuration:

  imap ;t <c-o>;t
  imap ;T <c-o>;T

You can transition the state forward and backwards by using t/T (Default: cit/ciT) if you use:

org = {
  org_todo = 't',
  org_todo_prev = 'T',
TODO state customization

By default they are TODO or DONE but you can define your own using the org_todo_keywords configuration. It accepts a list of unfinished states and finished states separated by a '|'. For example:

org_todo_keywords = { 'TODO', 'NEXT', '|', 'DONE' }

You can also use fast access states:

org_todo_keywords = { 'TODO(t)', 'NEXT(n)', '|', 'DONE(d)' }

Sadly you can't yet use different todo sequences.


TODO items can also have a priority, by default you have 3 levels A, B and C. If you don't set a priority it's set to B.

You can increase/decrease the priority with =/- (Default: ciR/cir):

org = {
  org_priority_up = '-',
  org_priority_down = '=',

I feel more comfortable with these priorities:

  • A: Critical
  • B: High
  • C: Normal
  • D: Low

This gives you room to usually work on priorities B-D and if something shows up that is really really important, you can use A. You can set this setting with the next snippet:

  org_priority_highest = 'A',
  org_priority_default = 'C',
  org_priority_lowest = 'D',


TODO items can also have timestamps which are specifications of a date (possibly with a time or a range of times) in a special format, either <2003-09-16 Tue> or <2003-09-16 Tue 09:39> or <2003-09-16 Tue 12:00-12:30>. A timestamp can appear anywhere in the headline or body of an Org tree entry. Its presence causes entries to be shown on specific dates in the agenda.

Date types

Meant to be used for elements of the org file that have a defined date to occur, think of a calendar appointment. In the agenda display, the headline of an entry associated with a plain timestamp is shown exactly on that date.

* TODO Meet with Marie
<2023-02-24 Fri>

When you insert the timestamps with the date popup picker with ;d (Default: <leader>oi.) you can only select the day and not the time, but you can add it manually.

You can also define a timestamp range that spans through many days <2023-02-24 Fri>--<2023-02-26 Sun>. The headline then is shown on the first and last day of the range, and on any dates that are displayed and fall in the range.

Recurring tasks

A timestamp may contain a repeater interval, indicating that it applies not only on the given date, but again and again after a certain interval of N hours (h), days (d), weeks (w), months (m), or years (y). The following shows up in the agenda every Wednesday:

* TODO Go to pilates
  <2007-05-16 Wed 12:30 +1w>

When you mark a recurring task with the TODO keyword ‘DONE’, it no longer produces entries in the agenda. The problem with this is, however, is that then also the next instance of the repeated entry will not be active. Org mode deals with this in the following way: when you try to mark such an entry as done, it shifts the base date of the repeating timestamp by the repeater interval, and immediately sets the entry state back to TODO.

As a consequence of shifting the base date, this entry is no longer visible in the agenda when checking past dates, but all future instances will be visible.

With the +1m cookie, the date shift is always exactly one month. So if you have not paid the rent for three months, marking this entry DONE still keeps it as an overdue deadline. Depending on the task, this may not be the best way to handle it. For example, if you forgot to call your father for 3 weeks, it does not make sense to call him 3 times in a single day to make up for it. For these tasks you can use the ++ operator, for example ++1m. Finally, there are tasks, like changing batteries, which should always repeat a certain time after the last time you did it you can use the .+ operator. For example:

** TODO Call Father
   DEADLINE: <2008-02-10 Sun ++1w>
   Marking this DONE shifts the date by at least one week, but also
   by as many weeks as it takes to get this date into the future.
   However, it stays on a Sunday, even if you called and marked it
   done on Saturday.

** TODO Empty kitchen trash
   DEADLINE: <2008-02-08 Fri 20:00 ++1d>
   Marking this DONE shifts the date by at least one day, and also
   by as many days as it takes to get the timestamp into the future.
   Since there is a time in the timestamp, the next deadline in the
   future will be on today's date if you complete the task before

** TODO Check the batteries in the smoke detectors
   DEADLINE: <2005-11-01 Tue .+1m>
   Marking this DONE shifts the date to one month after today.

** TODO Wash my hands
   DEADLINE: <2019-04-05 08:00 Fri .+1h>
   Marking this DONE shifts the date to exactly one hour from now.

SCHEDULED defines when you are plan to start working on that task.

The headline is listed under the given date. In addition, a reminder that the scheduled date has passed is present in the compilation for today, until the entry is marked as done.

*** TODO Call Trillian for a date on New Years Eve.
    SCHEDULED: <2004-12-25 Sat>

If you want to delay the display of this task in the agenda, use SCHEDULED: <2004-12-25 Sat -2d> the task is still scheduled on the 25th but will appear two days later. In case the task contains a repeater, the delay is considered to affect all occurrences; if you want the delay to only affect the first scheduled occurrence of the task, use --2d instead.

Scheduling an item in Org mode should not be understood in the same way that we understand scheduling a meeting. Setting a date for a meeting is just a simple appointment, you should mark this entry with a simple plain timestamp, to get this item shown on the date where it applies. This is a frequent misunderstanding by Org users. In Org mode, scheduling means setting a date when you want to start working on an action item.

You can set it with <leader>s (Default: <leader>ois)


DEADLINE defines when the task is supposed to be finished on. On the deadline date, the task is listed in the agenda. In addition, the agenda for today carries a warning about the approaching or missed deadline, starting org_deadline_warning_days before the due date (14 by default), and continuing until the entry is marked as done. An example:

* TODO Do this 
DEADLINE: <2023-02-24 Fri>

You can set it with <leader>d (Default: <leader>oid).

Using too many tasks with a DEADLINE will clutter your agenda. Use it only for the actions that you need to have a reminder, instead try to using appointment dates instead. The problem of using appointments is that once the date is over you don't get a reminder in the agenda that it's overdue, if you need this, use DEADLINE instead.

If you need a different warning period for a special task, you can specify it. For example setting a warning period of 5 days DEADLINE: <2004-02-29 Sun -5d>. If you do use DEADLINES for many small tasks you may want to configure the default number of days to 0. Most of times you are able to finish the task in the day, for those that you can't specify a different warning period in the task.

  org_deadline_warning_days = 0,
Date management
  org = {
    org_deadline = '<leader>d',
    org_schedule = '<leader>s',
    org_time_stamp = ';d',

To edit existing dates you can:

  • Increase/decrease the date under the cursor by 1 day with <shift><up>/`
  • Increase/decrease the part of the date under the cursor with <control>a/<control>x
  • Bring the date pop up with <control>e (Default cid)
  org = {
    org_change_date = '<c-e>',

To be able to use the bindings in insert mode too add below the org-mode configuration:

  imap ;d <c-o>;d
  imap <c-e> <c-o><c-e>

You can also use the next abbreviations:

  • :today:: expands to today's date (example: <2021-06-29 Tue>)
  • :itoday:: expands to an invactive version of today's date (example: [2021-06-29 Tue])
  • :now:: expands to today's date and current time (example: <2021-06-29 Tue 15:32>)
  • :inow:: expands to invactive version of today's date and current time (example: [2021-06-29 Tue 15:32]


You can also use tags to organize your items. To edit them use <leader>g (Default <leader>ot).

  org = {
    org_set_tags_command = '<leader>g',

When you press that key you can type:

  • tag1: It will add :tag1:.
  • tag1:tag2: It will add :tag1:tag2:.
  • Press ESC: It will remove all tags from the item.

Tags are seen as :tag1:tag2: on the right of the TODO item description.

Note: tags can't have spaces so use long_break instead of long break.

Tags make use of the hierarchical structure of outline trees. If a heading has a certain tag, all subheadings inherit the tag as well. For example, in the list

* Meeting with the French group      :work:
** Summary by Frank                  :boss:notes:
*** TODO Prepare slides for him      :action:

The final heading has the tags work, boss, notes, and action even though the final heading is not explicitly marked with those tags. You can also set tags that all entries in a file should inherit just as if these tags were defined in a hypothetical level zero that surrounds the entire file. Using a line like the next one:

#+FILETAGS: :Peter:Boss:Secret:

If you plan to use the Capture function on the file, add the FILETAGS like at the top of the file, otherwise it will end up in the middle of it as you capture new elements.


Lists start with a dash:

- Org bullets

To create new list item press <control><enter>.


Checkboxes or checklists are a special type of list:

- [ ] Item 1
  - [ ] Subitem 1
  - [ ] Subitem 2
- [ ] Item 2

If you're over an item you can create new ones with <control><enter> (if you have the org_meta_return = '<c-cr>' binding set).

You can change the checkbox state with <control><space>, if you check a subitem the parent item will be marked as started <3 automatically:

- [-] Item 1
  - [X] Subitem 1
  - [ ] Subitem 2
- [ ] Item 2

You can't yet manage the checkboxes as you do the headings by promoting, demoting and moving them around.

Follow this issue if you want to see the progress of it's children at the parent checkbox.

One final aspect of the org file syntax are links. Links are of the form [[link][description]], where link can be an:

A link that does not look like a URL refers to the current document. You can follow it with gx when point is on the link (Default <leader>oo) if you use the next configuration.

org = {
  org_open_at_point = 'gx',

Org provides several refinements to internal navigation within a document. Most notably:

  • [[Some section]]: points to a headline with the name Some section.
  • [[#my-custom-id]]: targets the entry with the CUSTOM_ID property set to my-custom-id.

When the link does not belong to any of the cases above, Org looks for a dedicated target: the same string in double angular brackets, like <<My Target>>.

If no dedicated target exists, the link tries to match the exact name of an element within the buffer. Naming is done, unsurprisingly, with the NAME keyword, which has to be put in the line before the element it refers to, as in the following example

#+NAME: My Target
| a  | table      |
| of | four cells |

Ultimately, if none of the above succeeds, Org searches for a headline that is exactly the link text but may also include a TODO keyword and tags, or initiates a plain text search.

Note that you must make sure custom IDs, dedicated targets, and names are unique throughout the document. Org provides a linter to assist you in the process, if needed, but I have not searched yet one for nvim.

  • URL (http://, https://)
  • Path to a file (file:/path/to/org/file). File links can contain additional information to jump to a particular location in the file when following a link. This can be:
  • file:~/code/main.c::255: A line number
  • file:~/*My Target: A search for <<My Target>> heading.
  • file:~/ A search for- a custom ID


Properties are key-value pairs associated with an entry. They live in a special drawer with the name PROPERTIES. Each property is specified on a single line, with the key (surrounded by colons) first, and the value after it:

* CD collection
** Classic
*** Goldberg Variations
    :Title:     Goldberg Variations
    :Composer:  J.S. Bach
    :Publisher: Deutsche Grammophon
    :NDisks:    1

You may define the allowed values for a particular property Xyz by setting a property Xyz_ALL. This special property is inherited, so if you set it in a level 1 entry, it applies to the entire tree. When allowed values are defined, setting the corresponding property becomes easier and is less prone to typing errors. For the example with the CD collection, we can pre-define publishers and the number of disks in a box like this:

* CD collection
  :NDisks_ALL:  1 2 3 4
  :Publisher_ALL: "Deutsche Grammophon" Philips EMI

If you want to set properties that can be inherited by any entry in a file, use a line like:

#+PROPERTY: NDisks_ALL 1 2 3 4

This can be interesting for example if you want to track when was a header created:

*** Title of header
   :CREATED: <2023-03-03 Fri 12:11> 

Code blocks

Org offers two ways to structure source code in Org documents: in a source code block, and directly inline. Both specifications are shown below.

A source code block conforms to this structure:

#+NAME: <name>
#+BEGIN_SRC <language> <switches> <header arguments>

You need to use snippets for this to be usable.

An inline code block conforms to this structure:



src_<language>[<header arguments>]{<body>}


  • #+NAME: <name>: (Optional) Names the source block so it can be called, like a function, from other source blocks or inline code to evaluate or to capture the results. Code from other blocks, other files.
  • #+BEGIN_SRC’ … ‘#+END_SRC: (Mandatory) They mark the start and end of a block that Org requires.
  • <language>: (Mandatory) It is the identifier of the source code language in the block. See Languages for identifiers of supported languages.
  • <switches>: (Optional) Switches provide finer control of the code execution, export, and format.
  • <header arguments>: (Optional) Heading arguments control many aspects of evaluation, export and tangling of code blocks. Using Org’s properties feature, header arguments can be selectively applied to the entire buffer or specific subtrees of the Org document.
  • <body>: Source code in the dialect of the specified language identifier.


When we no longer need certain parts of our org files, they can be archived. You can archive items by pressing ;A (Default <Leader>o$) while on the heading. This will also archive any child headings. The default location for archived headings is <name-of-current-org-file>.org_archive, which can be changed with the org_archive_location option.

The problem is that when you archive an element you loose the context of the item unless it's a first level item.

Another way to archive is by adding the :ARCHIVE: tag with ;a and once all elements are archived move it to the archive.

org = {
  org_toggle_archive_tag = ';a',
  org_archive_subtree = ';A',

There are some work in progress to improve archiving in the next issues [1](, [2]( and [3]( 

## Refiling

Refiling lets you easily move around elements of your org file, such as headings or TODOs. You can refile with `<leader>r` with the next snippet:

org = {
  org_refile = '<leader>r',

When you press the refile key binding you are supposed to press <tab> to see the available options, once you select the correct file, if you will be shown a autocomplete with the possible items to refile it to. Luckily there is a Telescope plugin.

Install it by adding to your plugin config:

use 'joaomsa/telescope-orgmode.nvim'

Then install it with :PackerInstall.

You can setup the extension by doing:


To replace the default refile prompt:

vim.api.nvim_create_autocmd('FileType', {
  pattern = 'org',
  group = vim.api.nvim_create_augroup('orgmode_telescope_nvim', { clear = true })
  callback = function()
    vim.keymap.set('n', '<leader>r', require('telescope').extensions.orgmode.refile_heading)
    vim.keymap.set('n', '<leader>g', require('telescope').extensions.orgmode.search_headings)

If the auto command doesn't override the default orgmode one, bind it to another keys and never use it.

If you refile from the capture window, until this issue is solved, your task will be refiled but the capture window won't be closed.

Be careful that it only refiles the first task there is, so you need to close the capture before refiling the next

The plugin also allows you to use telescope to search through the headings of the different files with search_headings, with the configuration above you'd use <leader>g.


The org agenda is used to get an overview of all your different org files. Pressing ga (Default: <leader>oa) gives you an overview of the various specialized views into the agenda that are available. Remember that you can press g? to see all the available key mappings for each view.

  global = {
    org_agenda = 'ga',

You'll be presented with the next views:

  • a: Agenda for current week or day
  • t: List of all TODO entries
  • m: Match a TAGS/PROP/TODO query
  • M: Like m, but only TODO entries
  • s: Search for keywords
  • q: Quit

So far the nvim-orgmode agenda view lacks the next features:

  • Custom agenda commands
  • These interactions with the items:
  • Remove it
  • Promote/demote it
  • Order it up and down

Move around the agenda view

  • .: Go to Today
  • J: Opens a popup that allows you to select the date to jump to.
  • <c-n>: Next agenda span (Default f). For example if you are in the week view it will go to the next week.
  • <c-p>: Previous agenda span (Default b).
  • /: Opens a prompt that allows filtering current agenda view by category, tags and title.

For example, having a file with headlines that have tags mytag or myothertag, and some of them have check in content, searching by todos+mytag/check/ returns all headlines that are in file, that have mytag tag, and have check in headline title.

Note that regex is case sensitive by default. Use the vim regex flag \c to make it case insensitive. For more information see :help vim.regex() and :help /magic.

Pressing <TAB> in filter prompt autocompletes categories and tags.

  • q: Quit
  agenda = {
    org_agenda_later = '<c-n>',
    org_agenda_earlier = '<c-p>',

Act on the agenda elements

  • <enter>: Open the file containing the element on your cursor position. By default it opens it in the same buffer as the agenda view, which is a bit uncomfortable for me, I prefer the behaviour of <tab> so I'm using that instead.
  • t: Change TODO state of an item both in the agenda and the original Org file
  • =/-: Change the priority of the element
  • r: Reload all org files and refresh the current agenda view.
  agenda = {
    org_agenda_switch_to = '<tab>',
    org_agenda_goto = '<cr>',
    org_agenda_priority_up = '=',
    org_agenda_set_tags = '<leader>g',
    org_agenda_deadline = '<leader>d',
    org_agenda_schedule = '<leader>s',

Agenda views:

  • vd: Show the agenda of the day
  • vw: Show the agenda of the week
  • vm: Show the agenda of the month
  • vy: Show the agenda of the year

Once you open one of the views you can do most of the same stuff that you on othe org mode file:

There is still no easy way to define your custom agenda views, but it looks possible 1 and 2.

Agenda searches

When using the search agenda view you can:

  • Search by TODO states with /WAITING
  • Search by tags +home. The syntax for such searches follows a simple boolean logic:

  • |: or

  • &: and
  • +: include matches
  • -: exclude matches

Here are a few examples:

  • +computer&+urgent: Returns all items tagged both computer and urgent.
  • +computer|+urgent: Returns all items tagged either computer or urgent.
  • +computer&-urgent: Returns all items tagged computer and not urgent.

As you may have noticed, the syntax above can be a little verbose, so org-mode offers convenient ways of shortening it. First, - and + imply and if no boolean operator is stated, so example three above could be rewritten simply as:


Second, inclusion of matches is implied if no + or - is present, so example three could be further shortened to:


Example number two, meanwhile, could be shortened to:


There is no way (as yet) to express search grouping with parentheses. The and operators (&, +, and -) always bind terms together more strongly than or (|). For instance, the following search


Results in all headlines tagged either with computer or both work and email. An expression such as (computer|work)&email is not supported at the moment. You can construct a regular expression though:

  • Search by properties: You can search by properties with the PROPERTY="value" syntax. Properties with numeric values can be queried with inequalities PAGES>100. To search by partial searches use a regular expression, for example if the entry had :BIB_TITLE: Mysteries of the Amazon you could use BIB_TITLE={Amazon}


Capture lets you quickly store notes with little interruption of your work flow. It works the next way:

  • Open the interface with ;c (Default <leader>oc) that asks you what kind of element you want to capture.
  • Select the template you want to use. By default you only have the Task template, that introduces a task into the same file where you're at, select it by pressing t.
  • Fill up the template.
  • Choose what to do with the captured content:
  • Save it to the configured file by pressing ;w (Default <control>c)
  • Refile it to a file by pressing ;r (Default <leader>or).
  • Abort the capture ;q (Default <leader>ok).
mappings = {
  global = {
    org_capture = ';c',
  capture = {
    org_capture_finalize = ';w',
    org_capture_refile = ';r',
    org_capture_kill = ';q',

Configure the capture templates

Capture lets you define different templates for the different inputs. Each template has the next elements:

  • Keybinding: Keys to press to activate the template
  • Description: What to show in the capture menu to describe the template
  • Template: The actual template of the capture, look below to see how to create them.
  • Target: The place where the captured element will be inserted to. For example ~/org/ If you don't define it it will go to the file configured in org_default_notes_file.
  • Headline: An optional headline of the Target file to insert the element.

For example:

org_capture_templates = {
  t = { description = 'Task', template = '* TODO %?\n  %u' }

For the template you can use the next variables:

  • %?:Default cursor position when template is opened
  • %t: Prints current date (Example: <2021-06-10 Thu>)
  • %T: Prints current date and time (Example: <2021-06-10 Thu 12:30>)
  • %u: Prints current date in inactive format (Example: [2021-06-10 Thu])
  • %U: Prints current date and time in inactive format (Example: [2021-06-10 Thu 12:30])
  • %<FORMAT>: Insert current date/time formatted according to lua date format (Example: %<%Y-%m-%d %A> produces 2021-07-02 Friday)
  • %x: Insert content of the clipboard via the "+" register (see :help clipboard)
  • %^{PROMPT|DEFAULT|COMPLETION...}: Prompt for input, if completion is provided an :h inputlist will be used
  • %(EXP): Runs the given lua code and inserts the result. NOTE: this will internally pass the content to the lua load() function. So the body inside %() should be the body of a function that returns a string.
  • %f: Prints the file of the buffer capture was called from.
  • %F: Like %f but inserts the full path.
  • %n: Inserts the current $USER
  • %a: File and line number from where capture was initiated (Example: [[file:/home/user/projects/myfile.txt +2]])

For example:

  T = {
    description = 'Todo',
    template = '* TODO %?\n %u',
    target = '~/org/'
  j = {
    description = 'Journal',
    template = '\n*** %<%Y-%m-%d %A>\n**** %U\n\n%?',
    target = '~/sync/org/'
  -- Nested key example:
  e =  'Event',
  er = {
    description = 'recurring',
    template = '** %?\n %T',
    target = '~/org/',
    headline = 'recurring'
  eo = {
    description = 'one-time',
    template = '** %?\n %T',
    target = '~/org/',
    headline = 'one-time'
  -- Example using a lua function
  r = {
    description = "Repo URL",
    template = "* [[%x][%(return string.match('%x', '([^/]+)$'))]]%?",
    target = "~/org/",

Use capture

The orgmode repository file organization

How to structure the different orgmode files is something that has always confused me, each one does it's own way, and there are no good posts on why one structure is better than other, people just state what they do.

I've started with a typical gtd structure with a directory for the todo another for the calendar then another for the references. In the todo I had a file for personal stuff, another for each of my work clients, and the Soon making the internal links was cumbersome so I decided to merge the personal and the into the same file and use folds to hide uninteresting parts of the file. The reality is that I feel that orgmode is less responsive and that I often feel lost in the file.

I'm now more into the idea of having files per project in a flat structure and use an file to give it some sense in the same way I do with the mkdocs repositories. Then I'd use internal links in the file to organize the priorities of what to do next.


  • As we're using a flat structure at file level, the links between the files are less cumbersome file:./*heading. We only need to have unique easy to remember names for the files, instead of having to think on which directory was the file I want to make the link to. The all in one file structure makes links even easier, just *heading, but the disadvantages make it not worth it.
  • You have the liberty to have a generic link like Work on project or if you want to fine grain it, link the specific task of the project
  • The todo file will get smaller.
  • It has been the natural evolution of other knowledge repositories such as blue


  • Filenames must be unique. It hasn't been a problem in blue.
  • Blue won't be flattened into Vida as it's it's own knowledge repository


Synchronize with other orgmode repositories

I use orgmode both at the laptop and the mobile, I want to syncronize some files between both with the next requisites:

  • The files should be available on the devices when I'm not at home
  • The synchronization will be done only on the local network
  • The synchronization mechanism will only be able to see the files that need to be synched.
  • Different files can be synced to different devices. If I have three devices (laptop, mobile, tablet) I want to sync all mobile files to the laptop but just some to the tablet).

Right now I'm already using syncthing to sync files between the mobile and my server, so it's tempting to use it also to solve this issue. So the first approach is to spawn a syncthing docker at the laptop that connects with the server to sync the files whenever I'm at home.

Mount the whole orgmode repository with syncthing

I could mount the whole orgmode directory and use the ignore patterns of syncthing, but that will make syncthing see more files than I want even though it won't sync them to the other devices. The ideal scenario is where syncthing only sees the files that needs to sync, so that in case of a vulnerability only a subset of the files is leaked.

Mount a specific directory to sync

An alternative would be to have a mobile directory at the orgmode repository where the files that need to be synced will live. The problem is that it would break the logical structure of the repository and it would make difficult to make internal links between files as you need to remember or need to search if the file is in the usual place or in the mobile directory. To avoid this we could use hard links. Soft links don't work well because:

  • If you have the file in the org repo and do the soft link in the mobile directory, syncthing won't know what to do with it
  • If you have the file in the mobile repo and do the soft link in the repository, nvim-orgmode won't be able to work well with the file. I don't know why but those files don't show when I search them in telescope (and I have symbolic links enabled in the config).

If we use this workflow, we'd need to manually create the hard links each time a new file is created that needs to be linked

This is also a good solution for the different authorization syncs as you can only have one syncthing directory per Linux directory so if you want different authorization for different devices you won't be able to do this unless you create a specific directory for that share. For example if I want to have only one file shared to the tablet I'd need a tablet directory.

Select which files to mount on the docker command

We could also select which files to mount on the syncthing docker of the laptop. I find this to be an ugly solution because we'd first need to mount a directory so that syncthing can write it's internal data and then map each of the files we want to sync. So each time a new file is added, we need to change the docker command... Unpleasant.

Use the org-orgzly script

Another solution would be to use org-orgzly script to parse a chosen org file or files, check if an entry meets required parameters, and if it does, write the entry in a new file located inside the directory you desire to sync with orgzly. In theory it may work but I feel it's too Dropbox focused.

Synchronize with external calendars

You may want to synchronize your calendar entries with external ones shared with other people, such as nextcloud calendar or google.

The orgmode docs have a tutorial to sync with google and suggests some orgmode packages that do that, sadly it won't work with nvim-orgmode. We'll need to go the "ugly way" by:

Importing the ics to orgmode

There are many tools that do this:

They import an ics file

Exporting from orgmode to ics

Other interesting features

Some interesting features for the future are:


Create an issue in the orgmode repository

  • Create a new issue
  • Create the minimal_init.lua file from this file
    vim.cmd([[set runtimepath=$VIMRUNTIME]])
    vim.cmd([[set packpath=/tmp/nvim/site]])
    local package_root = '/tmp/nvim/site/pack'
    local install_path = package_root .. '/packer/start/packer.nvim'
    local function load_plugins()
          { 'nvim-treesitter/nvim-treesitter' },
          { 'kristijanhusak/orgmode.nvim', branch = 'master' },
        config = {
          package_root = package_root,
          compile_path = install_path .. '/plugin/packer_compiled.lua',
    _G.load_config = function()
        highlight = {
          enable = true,
          additional_vim_regex_highlighting = { 'org' },
      vim.cmd([[packadd nvim-treesitter]])
      vim.cmd([[runtime plugin/nvim-treesitter.lua]])
      vim.cmd([[TSUpdateSync org]])
      -- Close packer after install
      if == 'packer' then
        vim.api.nvim_win_close(0, true)
        org_agenda_files = {
      -- Reload current file if it's org file to reload tree-sitter
      if == 'org' then
    if vim.fn.isdirectory(install_path) == 0 then
      vim.fn.system({ 'git', 'clone', '', install_path })
      vim.cmd([[autocmd User PackerCompileDone ++once lua load_config()]])
  • Add the leader configuration at the top of the file vim.g.mapleader = ' '
  • Open it with nvim -u minimal_init.lua

Sometimes doesn't work

Close the terminal and open a new one (pooooltergeist!).

Narrow/Widen to subtree

It's not yet supported to focus or zoom on one task.

Comparison with Markdown

What I like of Org mode over Markdown:

  • The whole interface to interact with the elements of the document through key bindings:
  • Move elements around.
  • Create elements
  • The TODO system is awesome
  • The Agenda system
  • How it handles checkboxes <3
  • Easy navigation between references in the document
  • Archiving feature
  • Refiling feature
  • # is used for comments.
  • Create internal document links is easier, you can just copy and paste the heading similar to [[*This is the heading]] on markdown you need to edit it to [](#this-is-the-heading).

What I like of markdown over Org mode:

  • The syntax of the headings ## Title better than ** Title. Although it makes sense to have # for comments.
  • The syntax of the links: [reference](link) is prettier to read and write than [[link][reference]], although this can be improved if only the reference is shown by your editor (nvim-orgmode doesn't do his yet)

Interesting things to investigate

  • org-bullets.nvim: Show org mode bullets as UTF-8 characters.
  • headlines.nvim: Add few highlight options for code blocks and headlines.
  • Sniprun: A neovim plugin to run lines/blocs of code (independently of the rest of the file), supporting multiples languages.