Bullet Journaling In Org Mode
I promised a guide to setting up bullet journaling with Emacs and org-mode. Here is that guide. If you want to know about my journey in note-taking apps and strategies, I'll refer you to my previous post here.
Getting started. Deciding on what to install.
There's lots of systems out there that this section could get infinite. I will not do that. Instead I'll tell you what I did in my case that:
- I work on a recent M3 MacBook Pro
- I'm already comfortable with
vim
, so an Emacs flavor that hasevil-mode
as a default fits me well. - My deep tinkerer spirit never died, but I have things to do here. So I need something a bit put together.
There's also some other decisions I took along the way. I'll add them here for completion.
- I'm interested in
org-roam
but I'm not sure yet. So I won't marry to it nor useorg-roam-dailies
for the journal. - I want the task-carry-over to be my main magic move. This has to work well. And well for me means that every day the extant tasks get ported over and the original ones are marked as POSTPONED, not deleted.
With these requirements in mind we decide on:
- Emacs installed through `brew` using this package: https://github.com/d12frosted/homebrew-emacs-plus?tab=readme-ov-file
- Doom Emacs as a distribution and initial config: https://github.com/doomemacs/doomemacs
- Org-mode for our typing and formatting needs: https://orgmode.org/
Org-journal for our daily notes: https://github.com/bastibe/org-journal
Let's get installing
Installation
This is the easy part. We install Emacs
brew tap d12frosted/emacs-plus brew install emacs-plus
The installation will take a while as it'll have to compile a bunch of things. But make sure to pay attention to the end. It'll tell you if you need to run any additional commands, e.g. to symlink the Emacs.app to the right place so you can open emacs with Spotlight or by navigating to the Applications folder as usual. It'll also tell you that you can start the Emacs service. Turns out Emacs does Make A Computer Slow and therefore has to run in the background as a daemon. If you don't start it it'll auto-start when you open it first, but if you're EmacsMaxxing you may consider enabling this to happen on startup.
With Emacs installed, we install the Doom Emacs distribution
git clone --depth 1 https://github.com/doomemacs/doomemacs ~/.config/emacs ~/.config/emacs/bin/doom install
Once we've installed Doom Emacs, we can jump straight to configuring it, but it'd make sense to do so in Emacs itself, wouldn't it? Then you need to sync your Doom configuration onto Emacs. This is how Doom works: we don't edit the actual Emacs config directly. We edit Doom-controlled files and then we apply those to Emacs. This is how you do it:
doom sync
Now, finally, you can CMD+Space
or whatever it is you do in your machine and run
Emacs. And we're going to jump straight to configuring it.
Installing packages
Doom Emacs has a pretty neat configuration system that allows us to pretty much do anything within the comforts of their little sanitized configuration system. It's not like we cannot run arbitrary Emacs code in our config, we surely can. But if we use their macros we're assured to get it working as the Doom devs intended. So we may as well do that.
The first place we encounter this is in the init.el
file. Open it by, in
normal-mode, doing SPC .
.
That's pressing the space bar and then the dot. The space bar is your leader key
in Doom and all their commands start with SPC
(or with M-SPC
, that is, ALT +
Space Bar, if you're in Edit mode).
Then navigate to ~/.config/doom/init.el
using your hacker skills and press
enter. You're now editing your initialization file for Doom Emacs. Congrats!
You'll see some informational comments up top. Read them. It'll tell you two critical things:
- Doing
SPC h d h
we can see the Doom documentation. - And with
K
over a package name, its documentation.
With these two you can start spelunking and deciding what to enable. But for
now I'll tell you what to do so you get my same initial config, which
essentially means enabling org-mode
.
Find ;;org
commented at some point of the file and replace that line with:
(org +journal +roam2 +pretty)
What we're asking is to have org-mode
enabled plus the journal, Roam-equivalent
and to be prettier.
With this done we should now save (:wq
, of course) and re-sync the Doom config.
We don't have to do this every single time, but we do if we've
enabled/disabled packages. Drop to a shell and do doom sync
.
Configuring Emacs
For configuration of our stuff we go to a different file in the same directory:
config.el
. Navigate to it (you know how now!) and have a look.
As before, you'll be graced with some amazing documentation up top. Read it. Then we start configuring.
Configuring org-mode
We start by defining a things. To do so, in elisp
, you setq
. For example, the
directories where we'll store things. The directory for org-mode files and the
one specific for journal entries.
(setq org-directory "~/org/" ;; This probably is already defined in your config org-journal-dir (expand-file-name "journal/" org-directory))
Keeping with configuring org-mode
, we now configure the TODO states. You can do
any set you like, but for me the sweet spot is TODO
, DOING
and DONE
for the
usual flow, POSTPONED
for tasks left for another day or carried over and
CANCELLED
for those that I decided against doing.
I also like that when a task is marked DONE the current timestamp is recorded.
We use a Doom macro, after!
, to ensure this configuration runs after org
itself
loads.
(after! org (setq org-todo-keywords '((sequence "TODO(t)" "DOING(i)" "|" "DONE(d)" "POSTPONED(p)" "CANCELLED(c)")) org-todo-keyword-faces '(("TODO" . +org-todo-active) ("DOING" . +org-todo-onhold) ("POSTPONED" . warning) ("CANCELLED" . shadow))) ;; Log state changes in a drawer, time on DONE (setq org-log-into-drawer t org-log-done 'time org-log-reschedule 'note org-log-redeadline 'note))
With this org-mode
itself is configured.
Configuring org-journal
Finally we reach the meat of the guide: setting up org-journal
. This package, by
default, does what we want: carrying over pending tasks from day to day. However
it does it in a way that's not exactly what I expected:
- It carries over only TODO items, but not e.g. DOING.
- Fully deletes the tasks from the previous day.
I don't like either. Luckily we can configure both. First, the list of tasks we
want to carry over is defined by the org-journal-carryover-items
. And for the
carryover operation, we can set the org-journal-handle-old-carryover
variable to
a function that will receive the list of carried over items and you can
essentially do anything with it. Let's start by defining that function.
;; Carryover handler: mark originals as POSTPONED (defun my/org-journal-postpone-old-carryover (entries) "Flip TODO/DOING → POSTPONED in *source* regions that were carried. Handles both ((BEG END) …) and ((BEG (END . TEXT)) …) shapes and edits bottom-up." (save-excursion (let ((inhibit-read-only t)) ;; Normalize to list of (BEG . END) markers and sort descending by BEG. (let* ((regions (mapcar (lambda (e) (let* ((beg (car e)) (end-spec (cadr e)) (end (if (consp end-spec) (car end-spec) end-spec))) (cons (copy-marker beg) (copy-marker end)))) entries)) (regions (sort regions (lambda (a b) (> (car a) (car b)))))) (dolist (r regions) (let ((beg (car r)) (end (cdr r))) (save-restriction (narrow-to-region beg end) (goto-char (point-min)) (while (re-search-forward org-heading-regexp nil t) (let ((st (org-get-todo-state))) (when (or (string= st "TODO") (string= st "DOING")) (org-todo "POSTPONED")))))))))))
With the function defined, now we use another Doom macro, use-package!
, to define
this variable and a few more. Feel free to investigate what each one does and
configure to your liking. They're quite self-explanatory, really.
(use-package! org-journal :after org :init (setq org-journal-date-prefix "#+title: " org-journal-file-type 'daily org-journal-file-format "%Y-%m-%d.org" org-journal-date-format "%Y-%m-%d" org-journal-time-format "(%H:%M)" org-journal-enable-agenda-integration t org-journal-find-file 'find-file) :config (setq org-journal-carryover-items "TODO=\"TODO\"|TODO=\"DOING\"" ;; Here we're saying both TODO and DOING are carried over org-journal-handle-old-carryover #'my/org-journal-postpone-old-carryover ;; Here we're referencing our function ;; This next one is the template that new files will have. You can do anything. Mine is tailored for a more complex org-roam setup. org-journal-file-header ":PROPERTIES:\n:ID: %Y-%m-%d\n:END:\n#+title: %Y-%m-%d\n#+startup: show2levels\n\n* Morning\n\n* Day log\n") )
And voilà, our setup is ready.
Finishing touches
At this point we can start using our setup. To apply the config, you can
evaluate the forms or just save and do SPC h r r
which will reload the whole
config for you. Pretty neat.
I have however added a couple of custom keybinds to help me. One, running the carryover manually. Useful for debugging but also for when you retroactively change a previous day and want to re-run the carryover for any reason.
;; Bind [SPC n j c] to manual carryover (map! :leader :desc "Manual journal carryover" "n j c" (cmd! (require 'org-journal) (call-interactively 'org-journal--carryover)))
And another one for a pretty specific need. When you use the normal command to
create/open today's note, it'll automatically add a timestamped section at the
end, with the goal of taking timestamped notes. However I many times want to
quickly go to the current journal entry just to look at the tasks. So I've added
a binding for that in SPC n j o
.
;; [SPC n j o] will open the current day note without adding a timestamp (map! :leader :desc "Open today's journal (no new entry)" "n j o" #'org-journal-open-current-journal-file)
Using your new journaling system
Using this setup can't be easier. Once you open Emacs, SPC n j j
will put you
into today's note. Write as you please (read the org manual for that) and add
your tasks. Use S-Right
and S-Left
to easily cycle between task states.
The next day when you start a new journal entry, yesterday's outstanding tasks will be automatically carried over.
Further reading
Use your daily journaling as an excuse to learn Emacs. By simply editing your
tasks every day you'll gain muscle memory both from the vim idioms provided by
evil-mode
as well as for the specific idiosyncrasies of Emacs. You'll get
comfortable with M-x
'ing to search for commands, memorize the Doom specific
bindings and become fluent at authoring org-mode
files just as you may already
be at doing Markdown.
And this will open the doors of the complete ecosystem to you. In my short
(literally a few days!) journey I've already started publishing this blog
entirely through Emacs using ox-publish
. And I've started taking all my notes
and organizing them in a very Roam/Obsidian way with org-roam
.
Give in and have fun!