Obsidian sync
Mirror your Dungeon Diary world into an Obsidian vault as Markdown files with YAML front-matter. One file per entity, one folder per kind. Edits stay local until upstream wins again.
Last updated May 2026 — native Obsidian plugin (v0.1.0) now ships alongside the CLI script.
Obsidian plugin (recommended)
The native plugin (dungeon-diary-sync v0.1.0) lives in the repository at plugins/obsidian/. Install it as a local community plugin until it reaches the official Obsidian store. No Node or CLI required — everything runs inside Obsidian.
Install
- Copy the
plugins/obsidian/folder from this repository into your vault's.obsidian/plugins/dungeon-diary-sync/directory. - In Obsidian: Settings → Community plugins → Enable “Dungeon Diary Sync”.
- Open the plugin settings and fill in the fields:
PAT dd_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Base URL https://dungeondiary.app (leave default unless self-hosting) Campaign slug ravenmoor-reach (leave blank for all campaigns) Vault folder dungeon-diary (subfolder inside your vault root) Auto-sync on Sync interval 15 min - Click the ribbon icon (🗡) or run Sync now from the command palette to trigger a manual sync. The status bar shows the result: “DD: synced Xm ago”, “DD: syncing…”, or “DD: error”.
Auto-sync — when enabled the plugin polls on the interval you set (default 15 min) and restarts the timer automatically after each sync. Disable it and use the ribbon or command palette for manual control.
How it works (CLI script)
A reference Node script polls the public /api/v1/me/* surface with a Personal Access Token, walks every entity in every campaign the token can see, and writes Markdown files into the vault folder you give it. The serializer is the same one the in-app “Download Markdown” button uses, so the output is identical.
Conflict policy: each file's front-matter carries dd_updated_at. On the next sync, the file is only overwritten when the server's timestamp is strictly newer. Manual edits survive until you change the same entity in Dungeon Diary again — at which point the server version wins. There's no three-way merge.
Quick start
- Mint a Personal Access Token at /dashboard/settings/api. Scopes needed:
read:campaigns,read:npcs,read:world,read:sessions. Copy the token — it's shown once. - Clone the Dungeon Diary repo (the script lives at
scripts/sync-to-vault.ts). Runpnpm installonce. - One-shot sync of every campaign your token can see:
pnpm sync:vault --pat=ddp_… --vault=~/Obsidian/dd - Watch mode — keeps running, polls on an interval:
pnpm sync:vault --pat=ddp_… --vault=~/Obsidian/dd --watch --interval=300 - Lock to a single campaign:
pnpm sync:vault --pat=ddp_… --vault=~/Obsidian/dd --campaign=ravenmoor-reach
Configuration via env vars
All flags can also be read from environment variables (handy for cron / launchd / systemd units):
# .env.local (or your shell rc)
DD_PAT=ddp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
DD_VAULT_PATH=~/Obsidian/dd
DD_BASE_URL=https://dungeondiary.app
# Optional: lock to one campaign
DD_CAMPAIGN=ravenmoor-reach
# Optional: poll cadence in seconds (default 300 = 5 min)
DD_INTERVAL=300Running on a schedule
On macOS / Linux, drop it in a crontab:
# crontab -e — sync every 5 minutes
*/5 * * * * cd /path/to/dungeon-diary && pnpm sync:vault --pat=ddp_… --vault=~/Obsidian/dd >> /tmp/dd-sync.log 2>&1On Windows, wrap the same command in a Task Scheduler entry. On any platform you can run with --watch inside a systemd service / launchd plist / pm2 process.
Vault layout
{vault}/
ravenmoor-reach/
npcs/edda-yarrow.md
npcs/cassian-vex.md
regions/saltmere-coast.md
settlements/halewick.md
factions/the-lampreys.md
lore/the-vow.md
quests/the-lantern-vow.md
…
another-campaign/
…Slugs are sanitized — any path separators in an entity slug are stripped so a hostile or weird slug can't escape its vault folder.
Front-matter
Each Markdown file starts with a YAML front-matter block. The standard fields from the API are emitted as-is, plus four sync-specific keys:
---
id: "npc_h7sj…"
slug: "edda-yarrow"
name: "Edda Yarrow"
race: "Halfling"
npcRole: "merchant"
npcStatus: "alive"
# …all other returned fields…
dd_id: "npc_h7sj…"
dd_kind: "npcs"
dd_synced_at: "2026-05-23T17:30:00.000Z"
dd_updated_at: "2026-05-23T14:12:09.443Z"
---
She owns the Black Stag because her husband owned it, and her husband
owned it because @the-lampreys let him.
…Extending it
The CLI script is intentionally short (~250 lines) and standalone — a starting point for custom integrations:
- Two-way sync — wire a file watcher in the vault to PATCH back to
/api/v1/me/campaigns/{slug}/{kind}/{entitySlug}. Use the front-matterdd_idas the upstream key and a last-write-wins policy. - Webhooks instead of polling — register a webhook subscription at /dashboard/settings/api that points at a relay you control. Write received events to a local file the script tails to avoid the poll cadence entirely. (The native Obsidian plugin uses polling; inbound webhooks require a public endpoint in the middle.)
Dungeon Diary