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

Newsletter Publishing

I publish my newsletter in two places:

  1. in the Newsletter section of the Garden, which gives me everything I need for nice linking/referencing/formatting
  2. on Substack, which provides a better subscriber/email-based experience

I first write my drafts in the newsletter/drafts folder, which is excluded from publishing. When it’s ready, I move it over to the main newsletter folder, where it’s automatically published on push like any other document in the Garden.

To convert it to a Substack version, I first run it through Augment with the following prompt:

Help me get `2025-07-06.md` ready for publishing on Substack.  
1. Replace all of the internal links (like [[How to score naneinf in Balatro (random seed)|Balatro]]) with a global external markdown link (like [Balatro](https://garden.bencuan.me/wip/How-to-score-naneinf-in-Balatro-(random-seed)). ALL of the external links are in subfolders, so you will have to find the correct folder that they're in as the second subpath. (For example: wip, about, community...). This can be inferred from the file structure of the vsh repository.  
2. Remove the markdown frontmatter.  
3. Remove all of the custom emojis (like :custom_leaf:), since they are not supported outside of the Garden website.  
4. Replace the top callout content with one that links back to it on garden.bencuan.me, saying "This post is a mirror of my Garden's [newsletter](https://garden.bencuan.me/newsletter). For a better experience, [click here](https://garden.bencuan.me/newsletter/2025-07-06) to view the original!  
5. Render the Markdown so I can copy-paste it into the Substack editor, since Substack does not support markdown paste.

Then, I copy-paste the Markdown from Obsidian into an app with a rich-text editor that supports Markdown paste (like Linear or Todoist), then copy-paste the formatted version into Substack.

The final manual steps:

  1. Unfortunately the agent isn’t great at inferring the correct subfolder names, so I go and verify all of the internal links.
  2. Replace all the markdown image refs with the actual images.
  3. Add the banner and subscribe button at the bottom.
  4. Schedule-send the post!