all repos — felt @ main

virtual tabletop for dungeons and dragons (and similar) using Go, MongoDB, and websockets

change label for aspect lock on token create form, adjust dice scroll logic
Derek Stevens
use #no3d hash fragment to disable 3d instead of making it mandatory; v0.2.4
Derek Stevens
disable leaflet 3D and bump to 0.2.3
Derek Stevens


– virtual tabletop for distributed cooperative storytelling –

Screenshot of Felt showing a hexgrid map on which a party of 4 humans and an alien dog thing facing off against two giant centipedes. Various UI windows are overlayed, some of them collapsed, showing the dice roller with dice log, and admin windows showing map and token management interfaces.


Felt is a lightweight (~210KB frontend!) webapp written in Go and vanilla Javascript which provides an agnostic virtual tabletop for battle maps, visual puzzles, or any other situation you may need a shared map in a tabletop RPG over voice/video chat.



Upon loading the application, the identity panel is expanded in the top-left. Enter your nickname here. It’s saved to localStorage.

Next, expand the goto panel and enter the table name and passcode to join. If they are correct, you will join the table and be presented with more panels.

The dice panel lets you roll dice with notes attached and provides a timestamped log of rolls.

The status panel is updated when the admin changes the table’s status, and can be used to display initiative order, other battle status, environmental or contextual notes, etc.

The token select panel provides a list of every existing token at the table. Clicking the name of the token shows a to-scale preview at the top of the list (with a button to dismiss it), which is resized according to the zoom level of the map. The button to the right of each token’s name can place the token on or remove it from the map.

Any user can move any token on the map by dragging it around. The map is pannable and zoomable as well. The UI theme is changeable from the drawer at bottom-left of the application, and it persists to localStorage.


Admin accounts can be created by the sysadmin using the invite command on the command line. It generates an encrypted token which can be placed after /register/ in the application URL to view an admin account creation form. The links are good for 15 minutes after generation.

After account creation, admins can login by providing their username in the identity panel and their password in the admin panel and hitting Login. The admin is presented with additional panels beyond a normal player (just the table panel at first).

The table panel provides the ability to create tables and view tables that belong to you. Clicking a table joins it.

Upon joining a table in this way, the content of the table panel changes. It provides a backlink to view the table list again, a textarea to change the table status, a button to delete the table, and a form to upload map images as well as a list of existing map images. The name of the image can be clicked to view it in a new tab, and the buttons to the right of each image name can be used to set the image as the map background or delete it.

IMPORTANT NOTE - Map images must be square; if your map is not square, add transparent padding to the image so it is.

Joining a table also grants the admin access to the tokens and sprites panels. The sprites panel allows the admin to upload, view, and delete images for use in tokens. The tokens panel gives an interface to create tokens as well as a list of existing tokens.

After hitting the New Token button, the list of tokens is replaced with a form. The sprite can be selected from a dropdown (which sets the other fields to resonable defaults), and the name, width, height, and coordinate origin of the token can be adjusted. There is a toggle for holding the aspect ratio of the token constant, and as changes are made a live preview similar to the one in the token select panel is updated (with the addition of a small pink cross indicating the coordinate origin).

Scene-building note - The coordinate origin determintes the layering of the tokens - tokens are ordered on the z-axis based on their vertical coordinate - eg scenic tokens which should be underneath characters can have their coordinate origin at the top of the sprite, and tokens which should be rendered above others in the scene can have their coordinate origin at or below the bottom edge.

In the tokens list, a token name can be clicked to live preview, or you can use the buttons at right to copy (opens the form with the token’s data - you can, eg, change the name and create the copy quickly) or destroy the token.

Creating or destroying tokens as well as changing the map causes updates to be sent to all clients at the same table in the same manner as (de)activating and moving tokens, updating status, or rolling dice.

build, run, deploy


  1. Clone this repository and cd into it.
  2. go mod tidy
  3. go build
  4. Simultaneously (from different terminals or services):
    • from the mongodb folder, copy the .env.example file to .env and adjust it, then run ./ build. After initial run, the build argument is not needed; this runs the database in a docker container. Make note of your settings/credentials for the mongodb URI.
    • from the root folder, run ./felt - a wizard asks for the mongodb URI (check mongo docs for this) the port, uploads folder, and max upload size
    • from the root folder, run ./felt invite to get a registration token and register an admin account as explained above
  5. When running behind a reverse proxy, you will need to make sure that the server can forward the Upgrade: websocket header.


The felt code (including the websocket server adapted from the chat example) is released under the MIT license; Leaflet, the slippy map library used in the frontend, is licensed under a 2-clause BSD license. Basically do what you will but give credit.

git clone