A markdown-driven map and feature planner for tabletop campaigns. Drop a
map PNG, place pins, paint terrain, draw rivers and roads, sketch the
features that drive your story, link them all with [[refs]]. Everything
lives as plain markdown and YAML files in data/ and config/ — no
cloud, no database, easy to back up, easy to diff.
Plonk in an map and add pins, each pin has a section for you to write information and attach images

Add quick details and double clicking pins can take you to the next map or select from drop down
Need to map out features of do a mind craph it can create and link many notes together
Story boards for routing or deicsion trees
Method to track characters and link them to points, maps stories etc
Add your own pin types, event tpyes or terrains
Built with Bun + Svelte 5 + Leaflet + xyflow. Runs locally. Stays local.
git clone <your-fork>
cd campaign-plannerThen double-click (Windows) or run from a terminal (macOS / Linux):
| Action | Windows | macOS / Linux |
|---|---|---|
| First-time setup | install.bat |
./install.sh |
| Launch the planner | run.bat |
./run.sh |
| Force-stop a stuck planner | kill.bat |
./kill.sh |
Uninstall (remove node_modules, optionally wipe data) |
uninstall.bat |
./uninstall.sh |
run.* boots the API (port 5174) and the Vite dev server (port 5173)
and opens your browser to http://localhost:5173. Close the window or
hit Ctrl+C to stop everything cleanly.
First-run tip: hit Import / Export → Import demo campaign to seed a small fictional starter set with a placeholder map. Useful for seeing what the planner can do without committing any of your own work yet.
install.bat creates Campaign Planner.lnk in this folder — a
shortcut that already has the icon set and points at run.bat.
- Right-click
Campaign Planner.lnk→ Pin to taskbar (or Pin to Start, or drag it to your Desktop). - Click the pinned icon any time to boot the planner.
The pinned tile uses the compass-rose icon. The cmd window that opens
when the planner runs uses Windows' default console icon — that's a
limitation of .bat files, not something a launcher can override.
Drag run.sh into Automator and save as an Application bundle, then
drag the .app to your dock. Or run from a terminal — your call.
Run uninstall.bat (Windows) or ./uninstall.sh (macOS / Linux). It's
interactive and asks two questions:
- Basic uninstall — removes
node_modules/,bun.lock, anddist/. Default: yes. This is the only thinginstall.*ever created. - Wipe campaign data — also empties
data/,assets/maps/,assets/locations/,assets/features/. Default: no. Use Import / Export → Export campaign before answering yes if you want a backup.
config/*.yaml is never touched — your custom pin types and world
scale survive an uninstall. To remove the planner completely, delete
this folder afterwards. Taskbar / dock shortcuts you created are
yours to manage.
- Bun ≥ 1.0 — run-time and package manager. Install
with
powershell -c "irm bun.sh/install.ps1 | iex"(Windows) orcurl -fsSL https://bun.sh/install | bash(macOS / Linux). - A modern browser.
campaign-planner/
├── README.md ← this file
├── LICENSE ← MIT
├── ROADMAP.md
├── package.json
├── index.html
├── icon.ico ← shortcut icon (Windows)
├── install.bat / install.sh ← first-time setup
├── run.bat / run.sh ← main launcher (pin this!)
├── kill.bat / kill.sh ← force-stop stuck processes
├── uninstall.bat / uninstall.sh ← remove install artefacts (interactive)
├── scripts/
│ └── rescale-coords.ts ← migrate pins after a world.size change
├── config/ ← user-editable knobs (also editable from the UI)
│ ├── app.yaml ← branding (name, tagline)
│ ├── world.yaml ← world size, units, ruler symbol
│ ├── map.yaml ← backdrop filename
│ ├── categories.yaml ← pin types (cities, dungeons, custom…)
│ ├── terrain.yaml ← terrain brushes (polygon + polyline)
│ └── movement.yaml ← ruler speed presets
├── data/ ← your campaign — created as you work
│ ├── locations/ ← pin entities (.md per pin)
│ ├── features/ ← feature graph nodes
│ ├── characters/ ← NPCs and PCs
│ └── terrain.geojson ← painted regions + drawn rivers/roads
├── assets/
│ └── maps/ ← drop your map PNG(s) here
├── samples/
│ └── demo-campaign/ ← bundled starter campaign
└── src/ ← Svelte 5 / Leaflet / xyflow source
Map tab — pick a pin type from the toolbar picker (search-as-you-type, grouped by category, recently-used row at the top). Click the map to drop a pin, give it a name, hit save. Pick a terrain brush to paint regions (water, forest…) or draw lines (rivers wobble organically thanks to a configurable meander; roads stay straight). The ruler tool measures any path on the map and shows walk / run / horse times for the distance.
Features tab — a force-directed graph of the pieces of your campaign
that link to each other. Five edge kinds (depends_on, blocks,
extends, conflicts_with, related) auto-render from frontmatter. Drag
nodes to taste; positions persist.
Characters tab — list view with search and per-character side-panel edit.
Side panel (right edge) — opens when you click any entity. YAML
frontmatter on top, markdown body below. Type [[ in the body to get an
autocomplete of every entity in the planner. Drop images straight into
the panel to attach them.
Settings (gear icon, top right) — edit branding, world scale and units, the map filename, the ruler speed table, and the full list of pin types and terrain brushes (add, recolour, reorder, delete) without touching YAML files.
Import / Export — top-bar dropdown. Export wraps data/, assets/
and config/ into a dated zip. Import reads any zip in the same shape
back. The bundled demo loads with one click.
Most settings are editable in the UI (Settings → World / Map / Ruler /
Branding / Pin types / Terrain). For batch edits or version-controlled
overrides, edit the YAML files in config/ directly — the planner
reads them on the next save or page reload.
# config/categories.yaml
- id: tavern
label: Tavern
dir: locations
color: "#a8703a"
group: SettlementsThen drop a pin from the Map tab — a generic markdown template is used when a pin type doesn't have a custom one in src/lib/templates.ts.
# config/world.yaml
world:
size: 12000
unit: foot
unit_symbol: ft
bounds_label: "12,000 ft × 12,000 ft"The map ruler and pixel-scale readout pick the new symbol up after a save.
Every entity is a single markdown file with YAML frontmatter:
---
id: riverhold
name: Riverhold
type: city
status: drafted
coords: [5500, 5000]
biome: temperate-river
tags: [capital, fortified]
images: [assets/locations/riverhold/main.png]
---
# Riverhold
Free-form prose…Entity types are open — add your own in config/categories.yaml and
they Just Work. The planner doesn't care about the rest of frontmatter
beyond the keys above and a few graph-edge buckets used by the Features
tab (depends_on, blocks, extends, conflicts_with, related).
See ROADMAP.md for v0.2 / v0.3 plans and explicit non-goals.
bun install
bun run dev # api on 5174, vite on 5173, both auto-restart
bun run build # production build of the Svelte app
bun x svelte-check # type checkThe server lives in src/server.ts — it's a small Bun
HTTP server that reads and writes data/, assets/, and config/. No
external services, no auth, intentionally simple.
MIT — see LICENSE. Use it however you like; attribution is appreciated but not required.