DEV Community

Cover image for Campaign Keeper – a session journal for tabletop RPG groups
John Munn
John Munn

Posted on

Campaign Keeper – a session journal for tabletop RPG groups

DEV Weekend Challenge: Community

This is a submission for the DEV Weekend Challenge: Community

The Community

I built this for tabletop RPG groups—especially Game Masters running long campaigns alongside busy adult lives.

Most campaigns don’t fall apart because people stop caring. They fall apart because continuity erodes. Notes get scattered, NPC details fade, players forget what happened three weeks ago, and the DM quietly becomes the sole keeper of the world’s memory.

I wanted to build something for that exact problem: a tool that helps preserve momentum between sessions without turning prep into admin work.

What I Built

I built Campaign Keeper, a lightweight campaign journal for tabletop RPGs.

It helps a DM keep track of:

  • session notes
  • player-safe recaps
  • DM-only notes and reflections
  • open plot threads
  • NPCs
  • players and character sheet links
  • locations
  • post-session player feedback

The core idea is simple: after each session, the DM logs what happened once, and the app turns that into a durable, evolving campaign record.

A few things I focused on:

  • Public vs. private memory Players get a clean recap link. The DM keeps the private truth, prep notes, and reflections.
  • Continuity over time NPCs, locations, and plot threads stay connected instead of disappearing into old notes.
  • Low friction This is meant to be fast to use after a session—not another workflow to maintain.
  • Tone I leaned toward an editorial campaign journal rather than a generic dashboard. I wanted it to feel authored, not automated.

Demo

Live app:

👉 https://campaign-keeper.netlify.app

GitHub repo:

👉 https://github.com/Tawe/Campaign-Keeper

Code

I wanted the code to reflect the same priorities as the product itself: keep continuity easy, keep public/private boundaries clear, and avoid turning the app into a pile of brittle CRUD screens.

A few pieces I’m especially happy with:

Revocable share links for player recaps

Instead of exposing raw session document IDs as public URLs, I moved recap sharing to generated share tokens. That means a DM can copy a link for players, rotate it if it leaks, or disable it entirely.

Files:

That ended up being a nice example of balancing product UX and security in a small app.

Ownership checks around server-side writes

The app uses server actions for mutations, but I didn’t want to trust raw client-supplied IDs. I added shared ownership guards so writes verify that the authenticated user actually owns the campaign or record being modified.

Files:

That work is invisible in the UI, but it made the app feel much less like a weekend prototype and much more like something I’d trust with real campaign data.

Private portrait storage instead of public image URLs

I added portrait uploads for players and NPCs, but kept them in private object storage and served them back through app routes instead of using public bucket URLs. That kept the feature simple for users without making everything publicly accessible by default.

Files:

That flow still has room to grow, but it gave me a clean path for portraits without exposing the storage layer directly.

A UI system that feels like a campaign journal instead of generic SaaS

The visual side mattered to me too. I didn’t want a fantasy app to look like default admin software, so I did a full styling pass toward a warmer editorial-journal feel while keeping forms and recap views fast to use.

Files:

That part was less about flashy visuals and more about giving the app a tone that matched the hobby and the use case.

Repo: https://github.com/Tawe/Campaign-Keeper

How I Built It

Campaign Keeper is built with:

  • Next.js
  • React
  • TypeScript
  • Firebase Auth (magic-link sign-in)
  • Firestore for application data
  • Private S3 storage for NPC and player portraits
  • Tailwind + shadcn/ui for the interface layer

A few implementation choices that mattered to me:

  • Server-side ownership checks All campaign, session, player, and NPC mutations verify ownership server-side instead of trusting client-supplied IDs.
  • Public recap sharing with revocable tokens Player recap links are token-based and can be rotated or disabled at any time.
  • Private portrait storage Images are stored privately and served through application routes rather than exposed as public object URLs.
  • Intentional UI I pushed the design away from stock SaaS patterns toward a warmer, journal-like feel that fit the hobby better.

Because this was a weekend build, I made a few pragmatic tradeoffs. The app is solid and demoable, but there are things I’d improve next:

  • stronger image moderation and normalization
  • deeper anti-abuse controls on the public feedback form
  • more onboarding polish and seeded demo data
  • broader automated test coverage

What I’m happiest with is that it solves a real problem for a community I’m part of. A lot of tabletop tools are either too generic or too heavy. I wanted this to feel focused: one place that helps a campaign remember itself.

Top comments (0)