banner

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:

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 files
  • 🟦 Blue = 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:

templates/default.md
---
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:

templates/daily.md
<%*
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.

meta/scripts/sh.js
// 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
meta/scripts/findDailyPreviousNote.js
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
meta/scripts/getPreviousContent.js
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:

  1. Commit/push the latest vsh changes to upstream
  2. 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.

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