# PlanarAlly: a self-hostable virtual battlemap with lighting and player vision

> PlanarAlly is an MIT-licensed web tool that adds virtual battlemaps to a tabletop roleplaying setup, with an infinite canvas, layers, dynamic lighting, per-player vision, an initiative tracker and multi-floor views, and it can be self-hosted so a group needs no external service. It suits a game group that wants a shared map without a subscription, and the server is Python while the client is TypeScript.

**Kruptein/PlanarAlly** — A companion tool for when you travel into the planes.

- Repository: https://github.com/Kruptein/PlanarAlly
- Website: https://www.planarally.io/
- Stars: 561 · Forks: 97
- Language: TypeScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/kruptein-planarally

## What the tool is, and the feature list that matters at the table

PlanarAlly is described as a companion tool for when you travel into the planes, and more plainly as a web tool that adds virtual battlemaps with various extras to your TTRPG and D&D toolbox.

The feature list is short, and each entry maps to a decision a group makes at the table.

Self hosting is the first, and it is the framing rather than a feature: you can run the software wherever you like without having to rely on an external service. That is the property that distinguishes it from the many hosted virtual tabletops.

Offline support comes next, and the reason given is specific: this tool can be used in a completely offline set-up for when you play D&D in a dark dungeon. That is not a hypothetical, and it is a real constraint on hosted tools that stream map tiles.

Then the map itself. Simple layers let you organise your scenes in layers for easier management, which is what you need when a town has a ground floor, an upper floor and a cave level, and the infinite canvas means a limited workspace is still not enough when the party walks out of the map you prepared.

Dynamic lighting is the immersion feature, and player vision is the rule that goes with it: you can limit vision to what your token or tokens can see, so a companion in a different room leaves you in the dark. That is the difference between a picture of a dungeon and a space the players are inside.

The initiative tracker is the practical one, since every group needs one and the built-in alternative is a spreadsheet or a phone app that one person has to drive. Floors closes the set, letting you look down upon lower floors when standing on a balcony, which is the multi-level feature that makes a vertical dungeon work in a 2D tool.

The tool is provided free to use and is open source, with the MIT licence.

## How it is deployed, and what the container tells you

The repository ships a Dockerfile, and reading it tells you the shape of the application better than any description would.

The build is a two-stage one. The frontend is built in a node:24-alpine stage, with python3, make and g++ added so that native npm modules can build. The package manifest and lock file are copied first and installed with npm ci, with the comment explaining why, that changes in code then do not require reinstalling all npm modules, which is the standard Docker layer caching trick. A build argument for the base path is declared, and the whole source tree is copied before the frontend build runs.

The final stage is a different stack entirely, based on the Astral uv image with Python 3.13 on bookworm-slim. The server is therefore Python, the client is TypeScript, and the two are built separately and joined in the image.

```dockerfile
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim

EXPOSE 8000

WORKDIR /planarally

VOLUME /planarally/data
VOLUME /planarally/static/assets
VOLUME /planarally/static/mods
```

Those three volumes are the state of the installation. Data is the database, static assets holds uploaded images and other files, and mods is where the mod API extensions live. Splitting them means an asset volume can be backed up or shared separately from the data, and the mods volume is the extension point.

The image runs as a non-root user with the numeric identifier 9000, and the directory ownership is set to match, and the entrypoint is dumb-init so that signals are handled correctly and a container that is stopped is stopped cleanly rather than killed. The command is uv run planarally.py, and the Python dependencies are installed with uv sync using the frozen lock file and no cache, with the pyproject.toml and uv.lock copied in their own layer for the same caching reason as the npm one.

Two environment variables are worth noticing. PA_BASEPATH is a build argument and an environment variable, so the same image can be served under a sub-path. And PA_GIT_INFO is set to docker plus the tag and the source commit, which is the sort of detail that makes it possible to tell from the interface which build a server is running, which matters when a group has more than one.

## The repository layout, and what a mod is

The tree is organised around the client and server split, and it is worth reading because the mod-api directory tells you that extensibility is a first-class part of the design.

There is a client/ directory for the TypeScript front end, a server/ directory for the Python application, a mod-api/ directory, an extra/ directory, and hooks/. The repository also carries an ARCHITECTURE.md, a CHANGELOG.md, a CONTRIBUTING.md, a .env file, a tsconfig.json, and the tooling configuration, with an .oxlintrc.json for linting and a .sonarcloud.properties for static analysis. Continuous integration is configured in a .circleci/ directory.

The mod API is the interesting part, because it is what separates PlanarAlly from a static map viewer. The container reserves a volume for it, the repository has a dedicated directory for it, and the deployment is a first-class part of the image rather than an afterthought. For a group that wants tokens that behave, a lighting source with rules attached, or a custom tool in the sidebar, that is the extension surface, and it is server-side and file-based, which means a mod is something you drop in and reload.

The hooks/ directory suggests the same idea applied to the lifecycle of the application, and extra/ holds additional material such as the example images used in the documentation, including the player light example that the project documentation credits to Gogots for the background map.

The default branch is dev rather than master, and the releases are versioned by year and sequence, with v2026.2 from 2026-09-13 and v2026.1.2 and v2026.1.1 both from 2026-02-22. The last push to the repository was on 2026-10-17. The primary language recorded for the repository is TypeScript, and the homepage is planarally.io, with a Discord server and a Patreon page linked from the project header.

## Running it for a group, and where the deployment decisions sit

The downloads section is short and gives the deployment advice directly. Typically only one person in your group should have to download and install PlanarAlly, alternatively you can use a publicly hosted version, and the releases are found on the release page.

That is the whole hosting model. One member of the group runs the server, everyone else connects to it from their browser, and the person running it does not have to be technical beyond being able to start a container. It is a self-hosted service with the social dynamics of a self-hosted service, which is worth thinking about before a group commits to it: somebody has to update it, somebody has to back it up, and when that somebody cannot make a session, the fallback is a hand-drawn map.

The publicly hosted version exists for the groups who do not want to run anything, and it is a sensible compromise for a trial.

The documentation lives at planarally.io/docs/, and there is a separate repository for the documentation itself, so the user guide is versioned independently of the application. The contributing guidance has three paths. If you have feedback or found a bug, the issues tab is the route, with the instruction to check whether the issue already exists first. If you want to work on the code, there is a development environment setup in the CONTRIBUTING document. And there is a Patreon page for sponsoring the work, which is how a volunteer project that several groups depend on stays alive.

The plugin point is worth restating for a group: the mod API and its volume mean the map can be made to behave in ways the base tool does not, and the cost of that is that somebody has to maintain those mods, and a mod that breaks after an update is a bad evening for the group.

## Where PlanarAlly is the wrong tool

The first limit is that it needs somebody to run a server. The project is careful to say that typically only one person in your group should have to download and install it, and that is the same sentence as saying that the group has a dependency on one member's machine and their willingness to maintain it. For a group that meets regularly and wants zero administration, a hosted service is the better trade, and the project offers one.

The second is the offline claim, which is a double-edged feature. Being usable in a completely offline set-up means the deployment has to be genuinely local, which is what self hosting gives you, but it also means the group is running a whole application on one machine, and a laptop that sleeps mid-session is a real scenario at a table where somebody's screen is also doing other things.

The third is the scope of the tool. It is a battlemap with extras: layers, an infinite canvas, lighting, vision, floors and an initiative tracker. It is not a virtual tabletop, it does not manage characters or sheets, it does not roll dice, and it does not run encounters. A group that wants one application for the whole game still needs the rest of the stack, and the initiative tracker is a convenience rather than a replacement for a good character system.

The fourth is the mod API, which cuts both ways. It makes the tool extensible enough for a group to shape it exactly, and it makes the tool's behaviour something that can break in ways the base application never would. A group with no interest in writing mods gets a stable product, and a group with mods gets a product it has to maintain across updates.

And the fifth is that the visual model is inherently two-dimensional. The floors feature lets you look down on a lower level, and that is a good approximation, but a vertical dungeon with real elevation, doors at different heights and a creature on a different level is a problem the tool approximates rather than solves. A group whose game is mostly planar is served well, and a group whose game is mostly vertical is served by an approximation.

## Conclusion

PlanarAlly is worth running for a table that wants the map to be part of the game rather than a shared image, because the vision and lighting model is what turns a picture of a dungeon into a playable space, because the offline support means a session in a cellar with no signal still works, and because the self-hosted route means the group's map and its campaign data sit on hardware they control. It is the wrong tool if you want a hosted service with zero setup, and it is the wrong tool if your group plays in person with a physical map. Before you deploy it, check three things: that you can run the container, since the image is built from a Node 24 frontend stage and a Python 3.13 server stage on the Astral uv base, with the server exposed on port 8000 and three volumes for data, static assets and mods; that you understand the mod API, because the repository has a mod-api/ directory and the container reserves a volume for mods, which means the extension surface is a supported part of the design rather than a hack; and that you plan for the server side, since the whole group usually shares one installation and the project's own guidance is that typically only one person in your group should have to download and install PlanarAlly.

## FAQ

### How do I run PlanarAlly for my tabletop group?

Typically only one person in the group downloads and installs it, and everyone else connects to that server from their browser, and there is a publicly hosted version for groups who would rather not run anything. The releases are on the GitHub release page, and the image exposes port 8000 with volumes for data, static assets and mods.

### What does the PlanarAlly container do at build time?

The frontend is built in a node:24-alpine stage with npm ci and npm run build, and the final image is based on the Astral uv Python 3.13 image with the server dependencies installed using uv sync with a frozen lock file. The container runs as a non-root user with dumb-init as the entrypoint and uv run planarally.py as the command, and PA_BASEPATH lets the same image be served under a sub-path.

### Can PlanarAlly work without an internet connection?

Yes. The project states that the tool can be used in a completely offline set-up for when you play D&D in a dark dungeon, which is the reason self hosting and offline support are listed as the first two features. The map, the layers, the lighting and the vision are all computed by the server you host locally.

## Sources

- [Kruptein/PlanarAlly on GitHub](https://github.com/Kruptein/PlanarAlly)
- [License: MIT](https://github.com/Kruptein/PlanarAlly/blob/dev/LICENSE)
- [Project website](https://www.planarally.io/)
- [README](https://github.com/Kruptein/PlanarAlly/blob/dev/README.md)
- [Releases](https://github.com/Kruptein/PlanarAlly/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kruptein-planarally
