Bullet Journaling In Org Mode
A guide to installing Emacs, Doom, org and org-journal
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-modeas 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-roambut I'm not sure yet. So I won't marry to it nor useorg-roam-dailiesfor 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: emacs-plus
-
Doom Emacs as a distribution and initial config: Doom Emacs
-
Org-mode for our typing and formatting needs: orgmode.org
-
Org-journal for our daily notes: 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 hwe can see the Doom documentation. - And with
Kover 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!