Summary
This page documents how and why I use Obsidian, how my notes are organized, and various improvements I’ve made to my setup over time.
Overview
Obsidian is a local-first Markdown-based notes platform. It’s similar to Notion and Evernote but comes with a lot more options for customization.
Nearly all of the words I’ve written down from 2020 onward have found their way to my Obsidian vault. I’ve recently decided to more directly publish notes from my vault to garden.bencuan.me.
In the past, I’ve used Obsidian for drafting before converting over to other locations for publishing, like:
- notes.bencuan.me for my college notes
- TurtleNet for homelab-related notes and guides
- blog.bencuan.me for long-form writing
- Substack for my weekly newsletter
Theming
My Obsidian vault uses the Catppuccin color theme.
My font selection mostly matches those selected for Garden Design.
Plugins
Here’s a list of the plugins I have installed, how they’re configured, and what they’re useful for.
Utility
Calendar
A simple calendar widget that allows me to easily create and view my daily notes.
Copy Image
Really simple plugin that lets me copy any image in Obsidian to my clipboard by right-clicking it. Super useful for converting an Obsidian note to a Substack post.
Daily Notes
I use my daily note as a scratchpad for todo’s, ephemeral thoughts, and other planning-related things that don’t need to live longer than a few days.
All of my daily notes are built from a custom Daily Notes Template that links back to the previous note and also copies all of its contents over. This way I can persist todo’s and remember if anything still needs to be addressed from yesterday before I erase everything (kind of like a whiteboard).
Quick Switcher ++
I’m very used to VSCode’s Cmd+P shortcut to jump to recent files:
Quick Switcher++ is almost identical to this behavior
my muscle memory lives on!
By default, Obsidian binds the Command Palette to Cmd+P, so I’ve rebound it to Cmd+Shift+P (also to match VSCode).
Shell commands
I use a custom git workflow with the Shell Commands plugin instead of the Obsidian Git plugin, because it seems to handle submodules incorrectly at time of writing. This is configured to handle both the main repository and the submodule structure:
echo $(cd content/vsh && git add . && git commit -m "obsidian vault backup: $(date "+%Y-%m-%d %H:%M:%S")" && git push origin HEAD && cd .. && git add . && git commit -m"vsh update: $(date "+%Y-%m-%d %H:%M:%S")" && git push)
This command is bound to Cmd+Shift+S
for quick execution.
Tasks
Easy checklists with Obsidian, includes completion dates.
- I can hit Cmd+L once to create a bullet point.
- If I hit Cmd+L twice, it becomes a todo item.
- If I hit Cmd+L a third time, the todo gets completed! [completion:: 2025-05-17]
Templater
Superpowered templates with support for custom bash/JS scripting and other neat features. See Templates for details on how I use it!
Aesthetics
File Color
I colorize my navbar folders so it’s clear what is published and what isn’t.
Green = published and available to the public at garden.bencuan.me
Red = private, explicitly excluded from publishing in
quartz.config.ts
Purple = top-level
index.md
’s and other filesBlue = internal Obsidian files and assets (not published, but also not sensitive)
Gold = friends-only
Iconize
Custom icons for all my folders!
Novel word count
So I can see my vault get big.
Style settings
Very fine-grained config for anything style/theme-related. I mostly use this to customize my header sizes/fonts/colors.
Templates
All templates are stored in a meta/templates
folder, which is excluded from Quartz publishing. I have the “insert template” command bound to Cmd+T
.
Default Garden Template
This template is bound to newly created files in every published folder. It looks vaguely like this:
---
date: <% tp.date.now("YYYY-MM-DD HH:mm") %>
stage: 0
---
> [!summary] Summary
>
- The frontmatter gets rendered by Quartz in various ways.
- The date is optional since Quartz fetches them from git history, but it’s nice to have in plaintext as well.
- The
stage
is 0 for sprout, 1 for blossom, 2 for evergreen. See Note Stages
Daily Notes Template
Whenever I click on a date on the calendar, it links to that date’s daily note.
A fully rendered daily note might look like this:
This daily note gets populated by Templater according to this template:
<%*
const w = await tp.user.sh(`curl -s "https://wttr.in/Millbrae?TmF0"`);
tR += '```\n' + w.out + '\n```\n';
-%>
<%*
const previousNote = await tp.user.findPreviousDailyNote(tp);
-%>
<< Jump to previous note: [[<% previousNote %>]]
---
<%*
const previousContent = await tp.user.getPreviousContent(tp);
tR += previousContent;
-%>
This template calls out to three custom scripts. I created a meta/scripts
directory and plopped all of these in there, then pointed Templater to this directory.
// https://forum.obsidian.md/t/templater-system-commands-file-lists-weather-and-git/36197
async function sh(cmd) {
const { promisify } = require("util")
const exec = promisify(require("child_process").exec)
const result = await exec(cmd)
return { out: result.stdout.trim(), err: result.stderr.trim() }
}
module.exports = sh
async function findPreviousDailyNote(tp) {
const currentDate = tp.file.title
const files = await app.vault.getMarkdownFiles()
const dailyFolder = "daily"
// Filter files to only those in daily folder and before current date
const dailyNotes = files
.filter((file) => file.path.startsWith(dailyFolder))
.filter((file) => file.basename < currentDate)
.sort((a, b) => b.basename.localeCompare(a.basename))
// Return the most recent previous note path, or null if none exists
return dailyNotes.length > 0 ? `daily/${dailyNotes[0].basename}` : null
}
module.exports = findPreviousDailyNote
async function getPreviousContent(tp) {
const files = await app.vault.getMarkdownFiles()
const dailyFolder = "daily"
const currentDate = tp.file.title
// Filter and sort daily notes
const dailyNotes = files
.filter((file) => file.path.startsWith(dailyFolder))
.filter((file) => file.basename < currentDate)
.sort((a, b) => b.basename.localeCompare(a.basename))
if (dailyNotes.length > 0) {
const mostRecent = dailyNotes[0]
const content = await app.vault.read(mostRecent)
// Split content at the first "---" and return everything after it
const parts = content.split("---")
if (parts.length > 1) {
return parts.slice(1).join("---").trim()
}
}
return ""
}
module.exports = getPreviousContent
Integrations
Quartz
I store my vault as a git submodule in my garden source code (https://github.com/64bitpandas/garden) under garden/content/vsh
. This allows me to
Publishing is a two-step process (which automatically runs on Cmd+Shift+S, see Shell commands:
- Commit/push the latest
vsh
changes to upstream - Update the
garden/content/vsh
submodule to
iCloud Sync
My entire outer garden
directory lives in iCloud so I get cross-device sync and backup. I pay $0.99 a month for 50GB, which is a bit cheaper than Obsidian Sync ($4/month).
don’t icloud sync the .git folder
Since the .git
folder changes rapidly and contains lots of blobs from the full githistory, and is already tracked in GitHub, I don’t need iCloud to sync it.
I initialized both the garden
and vsh
repositories with git init --separate-git-dir=/Users/bencuan/Documents/<garden|vsh>-git
to put their git directories in my local Documents
folder instead.
symlink to Documents for easy access
The iCloud folder is buried in some long Library/Mobile
subdirectory. To make it easier to access via command line, I have it symlinked to ~/Documents/garden
:
ln -s /Users/bencuan/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/garden ~/Documents/garden