Git-based Obsidian-to-Foundry journal importer. Sync an Obsidian vault (or any markdown-in-git tree) to Foundry VTT journals. Incremental syncs only fetch files that changed since the last commit.
Compatibility: Foundry VTT v13–v14.
- Pull markdown directly from GitHub — no upload step, no local file picker.
- Bulk markdown fetch via GraphQL — up to 50 files per request. A 500-file vault syncs in ~10 API calls instead of 500.
- Incremental sync via GitHub's compare API: only files that actually changed since the last sync are refetched.
- Deterministic journal IDs (SHA-1 of vault-relative path), so repeated syncs overwrite in place.
- Obsidian
[[wikilinks]],![[embeds]], and![[image.png|200]]resized images supported. - Per-file visibility via YAML frontmatter (
public: true→ OBSERVER, else GM-only). - Auto-generated References (backlinks) section on every page.
- Strips
> [!dm]Obsidian callout blocks. - Deleted files → deleted journals. Git is the source of truth.
- Push your vault to a GitHub repository. LFS works for media on public repos.
- Install the module and enable it in your world.
- Open Settings → Configure Settings → Module Settings → Vault Sync and set:
- GitHub Repository —
owner/name - Branch — defaults to
main - Subpath — optional; if the vault lives in a subdirectory of the repo
- GitHub Token — optional for public repos; required for private ones
- Root Folder — top-level journal folder name (default:
Vault)
- GitHub Repository —
- Open the Journal sidebar and click Sync Vault.
---
public: true # viewable by all players (OBSERVER)
# or: visibility: public
---Absent frontmatter → GM-only.
| Obsidian syntax | Becomes |
|---|---|
[[Note]] |
@UUID[...]{Note} |
[[Note|alias]] |
@UUID[...]{alias} |
[[folder/Note]] |
path-qualified resolution |
![[Note]] |
@Embed[...] |
![[image.png]] |
<img> referencing uploaded media |
![[image.png|300x200]] |
sized image |
Broken links render as struck-through text with the original wikilink syntax so you can spot and fix them.
Binary files (png, jpg, webp, mp3, ogg, mp4, pdf, …) are uploaded to worlds/<world>/vault-sync/<vault-path>. Paths are stable across syncs.
Git LFS: for public repos, GitHub's raw endpoint serves the binary transparently. For private repos + LFS, the module routes binary fetches through a small CORS proxy worker (default points at a shared instance). For better security, deploy your own — it's ~60 lines of code and takes 5 minutes on Cloudflare's free tier.
scripts/
├── main.mjs # init, sidebar button, sync dialog
├── settings.mjs # game.settings registration
├── sync.mjs # top-level orchestrator
├── github.mjs # REST compare/tree + GraphQL batch + raw content
├── importer.mjs # folder/journal/page upsert + delete
├── links.mjs # wikilink/embed transform, name map, backlink render
├── parser.mjs # frontmatter + DM-block stripping
├── media.mjs # binary upload to worlds/<world>/vault-sync/
└── ids.mjs # deterministic 16-char SHA-1-derived IDs
vendor/marked.esm.js # single bundled markdown parser
No build step, no bundler, no TypeScript. Plain ES modules.
./release.sh 1.0.1Requires jq, gh, zip, curl. Optional FOUNDRY_RELEASE_TOKEN in .env to publish to the Foundry package registry.