Repository navigation
Releases: CheekyChinchilla/CozyVTT
Release list
v1.5.4
CozyVTT v1.5.4: Mending
The long review after 1.5.0 found more than security and data-loss problems. It also found hundreds of smaller things a player or DM runs into: a sheet that works a number out wrongly, a button you can't read in a dark theme, a panel that ignores the keyboard, a change that reaches the table only after a reload. 1.5.1 and 1.5.2 fixed what could hurt an instance, and 1.5.3 moved CozyVTT onto current libraries. 1.5.4 is the release for everything else you notice: more than 200 fixes across the character sheets, the map, the table and self-hosting.
It also brings a few things you asked for. You can open a combatant's sheet and roll for them straight from the initiative tracker (#90), turn tokens to face any direction (#89), and keep using the campaign page in a narrow window (#85). Character sheets now take your theme, so a dark theme no longer shows a pale card with light text on it.
✅ No database change this time. Nothing you have stored is rewritten, and no setting is required. The backend and frontend are rebuilt and the nginx container is recreated. If you have edited
docker-compose.yml, read the stash steps in Updating your instance, and back up first.
✨ What's new
A combatant's sheet and rolls from the initiative tracker (#90)
Right-click a combatant in the Initiative tab for what right-clicking their token on the map offers about them: View Character Sheet for a player's character, and Roll... for whoever may roll it, which for the DM includes a creature's rolls from its stat block. Double-click a player character's row to open their sheet, or press Enter on a combatant's name. A combatant whose token is on another map offers nothing until the table switches to that map.
Tokens turn to face any direction (#89)
Right-click a token and choose Rotate Left or Rotate Right to turn it 45 degrees; the menu stays open for more turns. The DM can turn any token and type an exact angle in Facing in Edit Token, and a player can turn the tokens they control. The picture, or its letter, turns, while its rings, hit point bar and condition badges stay upright.
Continue anyway in a narrow window (#85)
Below 1024 pixels the campaign page showed only a Desktop Required notice, so a laptop with two windows side by side was turned away like a phone. The notice now has Continue anyway, which opens the table in its desktop layout, and your browser remembers the choice. A layout made for phones is still to come.
Character sheets take your theme
Every sheet uses the theme's paper, text and colours, with soft tints for the colours that mean something: hit points and damage, magic and attunement, notes. The header colour you pick is drawn everywhere a sheet uses it.
See and withdraw your invitations
Campaign Settings → Members lists the players you have invited who have not answered, under Invited, with an × to withdraw each one. An invitation sent from the app now runs out after 14 days, and one that has run out can be sent again.
Clear a sign-in lock from the Admin Panel
Ten wrong passwords in an hour lock an account from every address. Admin → Users now shows Locked until on a locked account, and Clear lock lets it sign in at once with its usual password, after asking. Each clear is a line in Admin → Activity.
More on the sheets
- Call of Cthulhu: contacts can be added, changed and removed on the Possessions tab, with a note under Luck Score and Wealth notes; Fighting and Firearms specialisations, sciences, languages and skills of your own can be added from the Skills tab and roll by their names
- D&D 5e: the Inventory tab shows each item's Value and the carrying capacity Strength gives, and the header names the player
And
- Jump to newest in the chat and the Dice panel, while you are scrolled up and something new arrives
- The backend's memory limit is a setting:
BACKEND_MEMORY_LIMITin.env, 512 MB when left out (Backend memory) - The Asset Library, the pickers and the Map Library load small previews of maps and tokens, so a large library opens faster
- A server admin moving someone's personal file into a campaign is written to the activity log
🐛 What's fixed
Character sheets, all three systems
- The built-in character templates follow the rules
- The Pathfinder 2e sheet shows and rolls the saves, Perception and skills its own numbers give
- Pathfinder 2e Bulk follows the rules on the sheet and in the editor
- Cthulhu Mythos typed on a Call of Cthulhu sheet lowers maximum Sanity
- Every Call of Cthulhu skill rolls from the sheet, named once
- A D&D 5e character's Proficiency Bonus follows their level
- Naming a D&D 5e spellcasting ability raises the spell save DC and spell attack
- The D&D 5e editor says what to fix before a save is refused, and the Pathfinder 2e and Call of Cthulhu editors say which name is missing
- Two people editing one character template no longer undo each other
- Save as Template works for a character with a picture
- Several more for each system. See the changelog
Maps, walls, lights and fog
- Players no longer see walls and doors through the fog of war
- Moving a token on a lit map with many walls no longer holds up the server
- Walls, doors and lights stay on the map's picture when you change its Width, Height or Grid Size
- Clicking a token beside a door picks the token up, or puts the one you are holding down there
- Fog of war fades away over the squares you reveal
- The AoE Shape tool's cone has the right shape for your game
- The right mouse button pans the map under every wall, fog and light tool, and opens no menu there
- Preview Player View can always be ended
At the table
- A token added, changed or removed reaches everyone at once, from wherever it was changed
- Edit Token shows changes made elsewhere while it is open, and no longer undoes them
- Damage from the minus buttons takes temporary hit points first, on the roster and in Edit Token
- A dice expression is read the same way everywhere, and one that cannot work is refused when it is rolled or saved
- What the table refuses is shown as a message, and the Dice panel shows only what happened to its own rolls
- Coming back after a dropped connection brings back what the DM changed meanwhile, and fills in the chat messages you missed
- Starting, pausing and ending a session from two tabs at once can no longer leave two sessions open, or an ended session marked Paused
- A chat cooldown the DM turns on during play reaches the players at the table
Campaigns, invitations and assets
- A campaign that exports can be imported again, however large
- Remove from Campaign in the Campaign Roster removes the character, for the DM too
- The Campaign Roster keeps up with characters deleted, created into the campaign, or moved to another one
- A new invitation appears on the dashboard without pressing Refresh
- The picture pickers can find every picture you may use, and the Asset Library's search and sort cover the whole library
- A file whose name has accented or other non-ASCII letters keeps its name
Reading, the keyboard and screen readers
- Coloured text reads clearly in every theme, and labels on the app's coloured buttons too
- Text boxes, checkboxes and dropdown lists are dark in a dark theme
- The DM's side panels work from the keyboard and are announced as dialogs
- The DM can reorder initiative and edit a value from the keyboard
- Screen readers hear new dice rolls and the connection status, and the dice panel's boxes and windows have names
- A screen reader names every box in the three character editors
- With one window open over another, Escape and Tab act on the top one only
Self-hosting
- Backups and restores work on an install whose database address carries settings of the app's, and keep to the schema it names
- A database password with a
%that encodes nothing works for every backup and restore - Stopping or upgrading CozyVTT during a restore lets the restore finish
- Stopping CozyVTT no longer waits 10 seconds while a player is connected
- The backend starts when its uploads or logs folder is on a NAS or network share that refuses a change of owner
- A mistyped
ASSET_UPLOAD_RATE_LIMITno longer turns the upload limit off
Keeping the instance up and players to what they may see
- Ten security advisories against five of the backend's libraries are cleared, and the ones against the tools that build and test CozyVTT
- A map, token or avatar picture is held to limits that keep an animation from swamping players' browsers
- A flood of sign-ins can no longer stall the server
- A crafted map in a campaign archive no longer costs the import far more memory than its size
- The live table connects only to the instance itself
- A game tab left open is disconnected when its sign-in ends
- Asking for something you may not see is answered as if it were not there
- With fog of war on, a player can open or close only a door beside a square revealed to them
- **An invited administrator has the fu...
v1.5.3
CozyVTT v1.5.3: Long Rest
1.5.1 and 1.5.2 fixed what a long review of CozyVTT found. Under them, CozyVTT still stood on older versions of the libraries it is built on: the database library, the web framework, React and the tool that builds the app were each a major version or more behind, and the tools that build and test it had published security advisories against them.
1.5.3 moves CozyVTT onto current versions of all of them, and adds no features. Your campaigns look and play as they did. Two small database updates run by themselves on the first start, and nothing else you have stored is rewritten.
On the way, every container gets less power. Each one drops the Linux capabilities its start-up does not use, and the backend and the bundled nginx can no longer change their own files, as the frontend already couldn't. Building CozyVTT now stops at any package that wants to run a program while it installs, unless CozyVTT has approved it.
⚠️ This release updates your database, by itself. One update records the table that keeps sign-ins, so nobody is signed out; the other clears the author of custom creatures whose author's account was already deleted, and keeps the creatures. All four containers are recreated. If you run commands in the backend container, add-u appuserafterwards. See Updating your instance, and back up first.
🔧 What changes
A newer database library
The backend moves to Prisma 7, which reaches PostgreSQL through the same driver the sign-in store already used and opens at most 10 connections for its own queries. Settings in DATABASE_URL, such as sslmode, now apply to every connection the backend makes. Deleting an account now clears the author of the creatures it made and keeps the creatures, as it already did for character templates.
Newer browsers
The app is built for Chrome or Edge 111, Firefox 114 or Safari 16.4, all from 2023, or newer. An older browser, such as Safari on an iPad that cannot update past iPadOS 15, no longer loads it. A phone or computer that installs its browser updates is not affected.
Current libraries throughout
Express 5 on the backend, React 19 in the app, and new versions of the libraries for sign-ins, email, exports and backups, dice totals, .env files, upload checks, animations and Markdown. Each does what it did. With reduce motion turned on, dialogs and panels still fade in where they stand.
HTTPS remembered for a year
A browser that has used your instance over HTTPS keeps to HTTPS for its name for a year, up from 180 days. Read HTTPS is remembered by browsers before you ever move the name back to plain HTTP.
IPv6 visitors counted by their network
An internet provider usually gives each home a whole block of IPv6 addresses, and a visitor could move between them. The request and sign-in limits now count every address in one /56 block as one, as they count everyone behind one IPv4 address.
Icons drawn a little differently
The icon set moves to a new version that redraws more than half of the icons CozyVTT uses, such as the ones for saving, deleting and settings. Each still stands for what it did.
🛡️ Security
Keeping each container to what it needs
- Each container keeps only the powers its start-up needs, such as changing a folder's owner or switching user, and drops the rest
- The bundled nginx and the backend can no longer change their own program files; the backend writes only to its uploads, backups and logs folders and its own
/tmp - A backup or restore stopped part-way no longer leaves its working copy of the database behind; the backend removes it each time it starts
Keeping the build honest
- Building CozyVTT stops at a package that wants to run a program while it installs, unless CozyVTT has approved it
- Security advisories against the tools that build and test CozyVTT are cleared, apart from one in a file-matching package with no fixed release, which goes with Tailwind CSS 4 in 1.5.4. None of their code is part of a running instance
- Two security advisories against the library that moves between the app's pages are cleared
Keeping campaigns apart
- The check of who belongs to a campaign takes the campaign from the request's address alone
📖 Documentation
- Every guide was checked against the code of this release: the deployment guide, the security notes, the user, player and DM guides, the API and WebSocket references and the developer guides
- The user guides now describe controls they had left out: the map toolbar, the roster's hit point buttons, Quick Roll, Remember me and how long a sign-in lasts, and the atmosphere player
- The deployment guide's troubleshooting says which start-up lines are harmless
- Known issues lists the bugs found while preparing this release, all of them already in 1.5.2 and planned for 1.5.4
- For scripts and integrations: the first entry under Changed in the changelog lists what a program driving the API may notice (#32)
⚠️ Behaviour changes
- The app needs Chrome or Edge 111, Firefox 114 or Safari 16.4, or newer
- Commands in the backend container take
-u appuser, and Prisma's own commands are./node_modules/.bin/prisma; a command that writes a file must write it under/tmp - A
docker-compose.override.ymlthat adds a program needing a Linux capability must give it back withcap_add - A file in
nginx/conf.dthat logs to a file of its own must log to/dev/stdout, or nginx stops withRead-only file system - HTTPS is remembered for a year, up from 180 days
- People in one home on IPv6 share the request and sign-in limits
VITE_API_URLandVITE_SOCKET_URLin.envare no longer read- The database's log shows two harmless
chmod: ... Operation not permittedlines each time it starts
⬆️ Updating your instance
💡 Back up first.
./backend/scripts/backup.sh
# From your CozyVTT directory
git pull origin main
docker compose up -d --buildIf you moved to 1.5.2 with git checkout v1.5.2, run git fetch --tags and git checkout v1.5.3 in place of the pull. Coming from 1.5.1 or earlier, read the upgrade notes of every release in between too, oldest first; the commands are the same.
All four containers are recreated: each drops the Linux capabilities its start-up does not use, the backend's and the bundled nginx's files become read-only, and the frontend image is no longer built with VITE_API_URL and VITE_SOCKET_URL. Compose makes two new volumes, nginx_temp and backend_tmp, which need no backup. Your campaigns, uploads and backups are kept; Docker's own log of each container starts afresh. nginx/nginx.conf does not change.
If you've edited docker-compose.yml, git stops at it; stash your edits, pull, and pop them back, as Updating after you've edited docker-compose.yml shows. If you deleted the nginx service to use your own proxy, the pop reports a conflict there once: keep it deleted, as the changelog's upgrade notes say.
Your own proxy in place of the bundled nginx needs nothing new: it sends /api/ and /socket.io/ to the backend on the page's own address, as Using an External Reverse Proxy shows. An install without Docker stays on Node.js 26 and upgrades with the usual commands in Without Docker.
After it's up:
- If you run commands in the backend container, add
-u appuser, such asdocker compose exec -u appuser backend ./node_modules/.bin/prisma migrate status - If your
docker-compose.override.ymladds a program that needs a capability, give it back there withcap_add(A container stops with "Permission denied", "Operation not permitted" or "Read-only file system") - If a file of yours in
nginx/conf.dwrites a log to a file, send it to/dev/stdoutinstead - If
.envstill setsVITE_API_URLorVITE_SOCKET_URL, you can delete both lines
No new settings are required, and none is added.
See CHANGELOG.md for the complete list of changes.
v1.5.2
CozyVTT v1.5.2: Extra Armor
1.5.1 stopped one account knocking the server over, and stopped edits disappearing. The same review went on to the places that matter most for an instance on the internet: how signing in works, what each player's browser is sent, what one account can make the instance do with its email and its disk, and how the bundled nginx and Docker are set up. It found many problems, most of them small.
A password alone could be guessed at from many addresses, a sign-in could live for ever, and an administrator account could rest on a password. Players' browsers were sent things the app then hid from them: the Keeper's Notes, which doors are locked, where the lights that are off sit. Any account could email everyone on the instance, read the whole user list, or slow the server for everyone with thousands of characters and templates, since anyone can start a campaign. And a visitor connecting over IPv6 could choose the address CozyVTT counted them as, which got round the sign-in limits.
So 1.5.2 is mostly security, with the fixes found on the way. Sign-ins end after 30 days, an account locks after ten wrong passwords from anywhere, and administrators get a week to set up two-factor authentication. Players' browsers are no longer sent the Keeper's Notes, locked doors or lights that are off. Invitations, uploads, characters and templates have limits far above what a table uses. The bundled nginx trusts only the proxies you name, every container runs with fewer privileges, and the fonts come from your own server. Under it all, CozyVTT moves to Node.js 26, since Node.js 20 stopped getting security fixes in April 2026.
✅ No database change this time. Nothing you've stored is rewritten, and there's no new required setting or manual step for most instances. All four containers are recreated. If a Cloudflare Tunnel or another proxy sits in front of CozyVTT, add up to three lines to
.envbefore you start it, or everyone shares one sign-in limit. See Updating your instance, and back up first.
✨ What's new
Administrators must use two-factor authentication
An administrator without it gets 7 days, with a banner on every page giving the date. After that the account can only set it up, change its password or sign out. Nobody else is affected. The Profile page now shows how many backup codes you have left, and after you sign in with one, the next page tells you, with a reminder to make new ones once three or fewer remain.
Signing in takes you where you were going
Open a campaign link while signed out, or come back to a page whose sign-in has ended, and signing in now goes on to that page. Only a page on your own instance is followed.
A tidier server
The Assets tab of the Admin Panel has a Leftover Files check, which lists files in the uploads folder that no asset names and deletes them only when you ask. The System Overview shows how much space is free on the disk holding the uploads, with a red warning once it is more than 80% full. Deleting an account now deletes its avatar and the personal files nothing uses, and the Uploader filter finds the files that accounts deleted before this release left behind.
Notes from campaigns you have left
A player removed from a campaign used to lose sight of their own notes and saved rolls from it. They now appear on the dashboard under Notes From Campaigns You Have Left, to read or download as one Markdown file. Nobody else can see them.
HTTPS settings that git pull leaves alone
HTTPS now goes in a file of your own in the new nginx/conf.d/ folder, and a proxy's addresses go in .env. Every earlier release that changed nginx/nginx.conf stopped git pull until your edit was set aside; this is the last one. The deployment guide has a complete nginx/conf.d/https.conf to copy.
Faster first visits, and no calls to Google
The app's scripts and styles, about 2 MB, now reach the browser compressed to under 600 KB, and the bundled nginx compresses the backend's answers too. The fonts are served by your own instance, so visitors' browsers no longer contact Google Fonts, and an instance with no internet access shows the right fonts.
🐛 What's fixed
Signing in and your account
- A server restart no longer sends everyone to the sign-in page, and a campaign page reconnects by itself however long the server was away
- The sign-in, sign-up, two-factor and password reset pages say why they refused, and a wrong password on the Profile page is said on the form
- The Forgot your password? page no longer says a link was sent when none was
- The Copy buttons for the two-factor key and backup codes work over plain HTTP, and a mistyped backup code can be corrected with Backspace
- Correct two-factor codes entered at the same moment from one address are all accepted
- The password strength meter no longer calls a password Strong that the page then refuses
- Several more, from the two-factor setup page surviving a reload to the browser's Back button. See the changelog
At the table
- On a lit map, a token two squares or taller sees from its middle
- A player can open or close only a door one of their tokens can see
- A campaign page says why its table has ended, and leaves it
- The token menu offers View Character Sheet only for a character in the campaign, and the quick editor no longer shows a token change that was not saved
- An Ogg sound recorded in Opus can be uploaded, and Ogg files carrying FLAC or Speex now play
- The Creature Library credits the D&D 5e SRD whenever it lists SRD creatures, with links to the SRD 5.1 and its licence
Character sheets
- A character sheet that cannot be shown no longer takes the whole page with it, and a Flexible sheet stored in a shape its page could not read opens again
- The D&D 5e Spells tab opens for every caster
- A refused save names the boxes to fix, in the editor at the table as well
- Every box on a sheet has a length limit, the lists on D&D 5e, Pathfinder 2e and Call of Cthulhu sheets hold up to 500 entries, and a published template keeps only the fields its sheet has
Admin Panel, backups and restore
- Only one restore runs at a time, and nothing changes the database while a restore replaces it
- A backup the restore refuses says why, and an uploaded backup left by a restore cut short is cleaned up
- Stopping or upgrading CozyVTT while a backup is being made lets it finish, waiting up to 10 minutes
- The status card shows how the server really is, and settings that failed to load can no longer be saved over the real ones
- The instance always keeps an administrator, and a campaign exactly one DM, whatever happens at the same moment
- The instance name is the browser tab's title and is used in emails
- Several more, from the Custom theme's colours to the Create User window. See the changelog
Self-hosting
- Reloading or bookmarking the Asset Library page opens it
- A large upload on a slow connection no longer fails with "Failed to upload asset"
- An error the backend cannot recover from is written to its log files, with the stack trace to attach to a bug report
- A session or rate-limit setting that is not a whole number no longer breaks sign-in or turns the limit off
- The backup and restore scripts accept a percent-encoded database password
- A failed Universal VTT import leaves nothing behind, and imports wait their turn and use less memory
Keeping accounts from being guessed or taken over
- Ten wrong passwords in an hour lock an account from every address, and Admin → Activity says which account and from where
- A sign-in ends 30 days after it was made, and an account keeps at most 20
- The setup wizard asks for a setup token, so a stranger cannot claim a fresh instance
- An administrator past the two-factor deadline cannot open a campaign's live connection either, only set up two-factor, change their password or sign out
- Password reset and invitation links are stored hashed, and one reset email goes out per account every five minutes
- An unknown email takes as long to refuse as a wrong password
- A production instance refuses a
SESSION_SECRETthat could be guessed - Everything an administrator does to someone's account is written to Admin → Activity, with who did it
Keeping secrets from players
- Call of Cthulhu Keeper's Notes are the Keeper's alone
- Players' browsers are no longer told which doors are locked or where the lights that are off are
- A token carried out of a player's sight leaves their map at that point
- Pictures in a document or note load only from the asset library, and links open in a new tab
- A map picture uploaded from Create Map goes to your personal library unless you choose the campaign's
Keeping one account from using the instance against others
- Invite Player searches by name and no longer lists everyone on the instance
- A DM can have the instance send 20 invitation emails an hour, and the subject no longer quotes the campaign
- Display names no longer keep invisible characters
- Only an administrator can import the SRD creatures
- The server reads a stored picture only once it has checked it is a PNG, JPEG, WebP or GIF image, which closes the way any account could reach an advisory in the image library
📖 Documentation
- For scripts and integrations, such as MCP bridges (#32): the first entry under Changed in the changelog lists every change a program may notice, with routes, events, status codes and limits
- The API guide shows what the server really sends, and...
v1.5.1
CozyVTT v1.5.1: Saving Throw
1.5.0 changed what players can see. Once it was out, the whole codebase got another careful read, and two things stood out.
The first: any signed-in account could stall or crash the server for every table on it. Anyone can start a campaign of their own, and nothing limited how big a map could be, how far away a wall could sit, how many messages a page could send, or how large a campaign archive could unpack to. A map tens of thousands of squares wide with fog turned on was enough to run the backend out of memory. On a server you share with your group, that's one curious player away from a cancelled session.
The second was #40: stats, equipment and spells reset after a save, and an hour lost typing them back in. That turned out to be several separate bugs in how sheets save, and the review found more of the same kind across the app. Walls undone onto the wrong map, Edit Map clearing the spirit layer, a token template losing its hit points on any save with its HP bar hidden, a D&D sheet keeping only one of two features with the same name.
So most of 1.5.1 is fixes. There are now limits on what one account can do, set far above anything a real table sends, so you should never notice them. Your edits stop disappearing in a long list of places they used to. And the backend's logs finally say what went wrong, so the next bug report is easier to chase down.
✅ No database change this time. Nothing you've stored is rewritten, and there's no new required setting or manual step. The upgrade recreates all four containers, which clears Docker's old container logs, and if you've edited
nginx/nginx.confordocker-compose.yml, git will stop at them. See Updating your instance, and back up first.
✨ What's new
Deleting asks first, and tells you what still uses a file
Deleting a picture or sound that a map, token, character, template, creature or the ambient sound still uses now stops and lists where it's used. It's only removed if you choose Delete anyway. Deleting a backup, changing someone's role, and deleting a creature, a token template or a token from the map all ask first too, where they used to act on one click.
D&D 5e skills and saving throws take bonuses the sheet can't work out
Each skill and saving throw has a box beside its total for other bonuses: Jack of All Trades, Aura of Protection, a Ring of Protection. The sheet and the dice include it. When you open an existing character in the editor, any difference between its stored totals and the worked-out ones goes into those boxes, so no total changes.
Big campaigns move between servers
Campaign archives up to 500 MB import through the bundled nginx, where imports used to stop at 55 MB. An export is sent as it's made, so a big one no longer risks stopping the server, and Chrome and Edge let you choose where to save it. An import brings in the whole campaign or nothing, and when it finishes it lists anything it Changed to fit or Left out, with the reason. Include audio assets now really includes your atmosphere's sound, and maps arrive in the order they were in.
Logs you can actually use
Errors used to be logged as an empty {}. Each one now says what went wrong and where. Log files rotate at 10 MB on their own, five kept of each, and Docker's log of each container is capped the same way, so logs can no longer fill the disk. Email addresses in the logs are cut to their first letter and domain.
🐛 What's fixed
Character sheets
- The Characters page shows a character as you last saved it (#40). It could show the version from before your save, and saving again was then refused as out of date
- A save refused because the character changed keeps your edits (#40). The editor puts your changes onto the newest version and shows a This character has changed box, where you pick which value to keep for anything both of you changed
- What you type while a sheet is saving is kept, in every game system
- D&D 5e features that share a name are all kept, such as Ability Score Improvement at level 4 and again at 8
- Call of Cthulhu Dodge keeps the points put into it instead of resetting to half DEX each time the editor opened
- Pathfinder 2e characters made from a built-in template keep their skill bonuses. The Level 1 Fighter's Athletics no longer drops from +6 to +3
- Typing a list works: "Common, Elven" no longer comes out as "CommonElven" on Pathfinder 2e and Call of Cthulhu sheets
- Editing a token template no longer deletes its hit points, and Edit Template keeps your sheet edits when a save fails
- Flexible sheets keep everything stored in them, and cancelling a D&D 5e editor leaves the sheet as it was
- Several more, from D&D 5e languages to colour choices. See the changelog
Maps, walls and lights
- Wall, door, light and fog changes made at the same moment are all kept. Placing a door on a wall could lose the door after a reload
- Undo after switching maps no longer puts the old map's walls on the new one
- Typing in the chat box no longer deletes or moves the selected walls
- The DM sees doors players open, and the DM's next wall edit no longer closes them again
- Reconnecting brings back doors and lights changed while you were away
- Saving Edit Map no longer removes the spirit layer picture, and switching lighting or fog no longer brings back an old map picture
- Walls, doors and lights can be drawn on a map opened over plain HTTP from another device, such as a laptop on your home network
- Tokens always stand on whole squares, all of them on the map
- The DM's tool panels keep one width when Fog of War opens
At the table
- The dice log keeps up with new rolls once it holds 50
- Typing in a token's stat block no longer resends the whole map to every player on each keystroke, and dragging the ambient volume slider no longer restarts everyone's sound at each step
- A chat cooldown longer than a minute lasts its full length
- Closing a document you're writing asks before throwing the text away
- Markdown documents opened from the Documents page are formatted
- Audio tracks play correctly from any point, and seeking no longer leaves old files open on the server
Campaign archives and Universal VTT
- A failed import leaves nothing behind, and an import no longer drops whole maps over one token it couldn't store
- Imported creatures and templates can be placed on a map and edited
- A refused Universal VTT import says why, and a crafted one can no longer exhaust the server's memory
- Exporting a map to a Universal VTT file is quick on large maps and keeps windows and lights as they were
- Uploads are recorded as what they really are, so a real picture gets its thumbnail, and file names keep their double quotes
Self-hosting
- The backend keeps running when the database restarts, as a database upgrade or a host reboot does
- Stopping, restarting or upgrading is quick and clean. The backend now finishes what it's doing and exits, usually within a second, where it waited ten seconds and was killed
- The site no longer answers 502 after only the backend is recreated, for example after a change to
.env - A big import or restore on a slow connection is no longer cut off after five minutes
- Correct sign-ins arriving together no longer lock an address out. Only wrong answers count now, which matters for a household or club signing in at once, or anyone behind a proxy that shows every visitor as one address
- The character maintenance commands report real problems, and end with an error status when they fail
One account can no longer take the server down
- Maps, tokens, stat blocks and wall positions have limits, so a huge map or a wall placed millions of pixels away can't stop the server
- Crafted campaign archives and damaged uploads can no longer crash or stall the server, and an import takes only the kinds of file an upload would
- The live table has a limit per account on chat, hit points, hit dice, token moves and the DM's controls, and an account can hold 40 connections at most
- Specially crafted text in an email address, an atmosphere filter, a backup or a hit dice entry can no longer freeze the server or a browser
- A client can no longer fill the disk through the server's log, and a malformed request is no longer logged with text that could include a password
📖 Documentation
- For scripts and integrations, such as MCP bridges (#32): the first entry under Changed in the changelog lists every change a program may notice, with routes, events, status codes and limits
- The WebSocket guide lists the limit for every live-table event
- The deployment guide's section on the API documentation is corrected. It recommended publishing the docs as if CozyVTT had a public API
- The proxy advice covers Universal VTT files and campaign imports, with the sizes and time an outside proxy needs
- The DM Guide's map export and campaign import sections match the app, including the largest campaign an import accepts
⚠️ Behaviour changes
- A map can be 1 to 500 squares on each side and hold 1,000 tokens, and a creature's stat block up to 64 KB, about ten times the largest monster in the SRD. Maps already over these keep loading and keep their tokens. Only fog and explored areas are refused on a map over 250,000 squares in all
- A PDF is no longer accepted as a map picture. It could never be shown on the...
v1.5.0
CozyVTT v1.5.0: Lights Out
The first real session run with dynamic lighting after 1.4.0 went about as well as you'd guess. Fog of war darkened the map but you could still see the artwork through it, and it couldn't be turned off. Every token could see everything in its line of sight whether there was a light or not, so the torch you placed was decoration. Two issues landed the same day saying so (#66, #68), and they were right.
So this release rebuilds how a player sees the map. Fog hides it. Light and darkvision decide what you can see in the dark. Places you've explored stay on your map in grey. The DM can preview the table through any player or any single token, which is what an in-person game on a projected screen actually needs (#67).
Fixing that turned up a bigger problem underneath. The server was sending players more than they were supposed to see and trusting the browser to hide it: a hidden monster's DM notes, an NPC's exact hit points in the initiative tracker, maps the DM hadn't shown yet. All of that is filtered on the server now, per player, before anything leaves it. A large security pass came along with it, covering sign-in, MFA, sessions, backups and every place a client can send data in.
And if you tried to restore a backup made from the Admin Dashboard on 1.4.0, it failed. Those backups were never damaged, and they restore on 1.5.0.
⚠️ This release migrates your database. It adds three settings to every map (fog on or off, Global Illumination, and remembering explored areas) and one new, empty table for explored areas. It also lets two columns be empty so a deleted account's dice rolls and uploads can stay. Nothing existing is changed or removed, and existing maps keep their fog and lighting behaviour. The backend applies it on start. Unlike 1.4.0, there are a couple of things to do afterwards. See Updating your instance, and back up first.
✨ What's new
Fog of war that actually hides the map
(#66) Unrevealed fog is now solid black for players, drawn over everything including walls and doors. The DM still sees through it at half strength. Fog can be turned on or off per map, at the top of the Fog of War panel or in Edit Map, and new maps start with it off. Turning it off keeps what was revealed, ready for when you turn it back on.
Light and darkvision decide what players see
(#68) A player now sees what their tokens' darkvision reaches in the dark, plus whatever a light source lights. Bright light shows clearly. Dim light, and darkness inside darkvision, show half dark. A token always sees the square it's standing on. Darkvision is set per token in grid squares, and only the DM can change it, since it decides what the server sends.
If your table liked the old behaviour, tick Global Illumination on the map and everything in line of sight is lit again. It's on for every map made before this release, so nothing changes until you turn it off.
Maps remember what each player has explored
Ground a player's tokens have seen stays on their map, greyed and darkened, once it's out of sight. It's kept on the server, so it survives a reload or a different device. Remember Explored Areas in Edit Map turns it on or off per map, and Reset explored areas in the Fog of War panel wipes everyone's memory of a map.
Preview the table through a player or a token
(#67) Preview Player View used to combine every token's sight, monsters included, which showed more than any player could. It now shows one chosen player's view, or one token's, or All player tokens for the whole party. That last one is for tables that share a projected screen and have no player accounts to preview as.
Obscure a token's identity
Right-click a token and pick Obscure Identity. Players see a grey shape with a question mark, called "Unknown creature" in the hover card, the initiative tracker and the dice log. Its name, picture, hit points and conditions never reach their browser. The DM still sees it, with a small ? badge. Reveal Identity undoes it.
Each vibe period can play its own music
The period editor's audio box used to save a note nothing ever read. It's now a track picker. Switching to a period starts its track for the whole table, and the Atmosphere panel still works as a live override on top.
🐛 What's fixed
What players are sent
- Players are sent only what they may see of a token. Hidden tokens, DM notes, stat blocks and hit points with the HP bar off are stripped on the server for each player
- The initiative tracker no longer shows players exact hit points or hidden creatures
- Maps the DM hasn't shown yet are private to the DM, including their walls, lights and fog
- Token moves only reach players who would be sent that token, so a hidden creature's movement doesn't leak its position
- A live connection is only ever in one campaign (#71). A socket that switched campaigns stayed in the old one and could receive its DM view
- Spectators can't move or edit tokens through the API, and only the DM can resize a token (#70)
Sign-in and accounts
- A page on another site can no longer act as you while you're signed in
- MFA backup codes are harder to guess, stored like passwords, and work once. Authenticator codes also work once, and wrong codes are rate limited per account
- Only the newest password reset or invite link works, and only once
- The forgot-password page no longer reveals which addresses have accounts by how long it takes
- Each sign-in gets a new session, and signing out or deleting an account closes its live connections too
- An instance can no longer lose its last administrator
- Deleting an account works and keeps that person's dice rolls and uploads
Backups and restore
- Backups made from the Admin Dashboard restore again, including ones made on 1.4.0. A backup from an older version, or from a database user with a different name, restores into a newer install
- A restore checks the file first and keeps a copy of what it replaces
- The restore script no longer deletes your database before the backup has loaded
- A crafted backup file can no longer run shell commands in the backend container
- Backups no longer contain sign-in sessions, are readable only by the backend's user, and live outside the uploads folder
- A backup that fails partway no longer takes the backend down with it
At the table
- Character sheet editors keep your changes through a sign-out, a refused save, or Escape, and only ask "Discard Changes?" when there's something to discard
- A sheet editor with unsaved changes keeps you signed in, so a long writing session doesn't time out underneath you
- Saving a sheet no longer undoes hit point changes the DM made in the meantime
- The initiative tracker follows the token, keeps its entry when a token moves maps, and ends the fight when the last combatant goes
- A token moved to another map keeps all its details, and moving several at once no longer loses or doubles any
- Messages and rolls sent during a reconnect are no longer missed
- The Token Templates list shows each template's picture again
- A long list of sheet fixes across D&D 5e, Pathfinder 2e and Call of Cthulhu. See the changelog
Self-hosting
- The bundled nginx forwards
/health(#72) SESSION_MAX_AGE,REMEMBER_ME_MAX_AGEandLOG_LEVELin.envnow work on Docker- Behind a Cloudflare Tunnel or another proxy, one person's wrong passwords no longer lock everyone out
- Malformed requests from scripts get a 400 that says what's wrong instead of a 500
- The API's character validation check reports real problems (#69). It used to say every sheet was valid
📖 Documentation
- The API specification was checked against the code route by route. Every route is documented, with the fields, limits, status codes and side effects it actually has
- The WebSocket reference now says correctly who may send each event, and its check fails on any difference from the code
- The DM, Player and User guides cover fog, lighting, darkvision, explored areas, previews and obscured tokens
- Known issues deferred to 1.5.1 are listed in
docs/FUTURE_FEATURES.md, so you can see what's known before you hit it
⚠️ Behaviour changes
- Fog now hides the map completely for players. Existing maps keep fog on, with the same areas revealed
- A player or spectator with no token on a lit map sees nothing. To let a spectator watch a lit map, turn dynamic lighting off for that map
- Explored areas appear in grey on lit maps, including existing ones
- Unlit ground is fully dark where it used to be nearly dark
- New maps start with fog off
- A token belongs to whoever is set as its controller. A character's token set to "Nobody (DM controls)" no longer gives that character's player sight
- Spectators can chat but can't roll dice
- Passwords need at least 12 characters, with upper and lower case, a number and a special character. Existing passwords keep working until they're changed
- Existing MFA backup codes stop working. Your authenticator app is unaffected...
v1.4.0
CozyVTT v1.4.0 — Removed, But Still At The Table
A DM removes a disruptive player from their campaign. The player's browser keeps going: still
sending chat, still rolling dice, still watching every token move. Nothing told them to stop,
because nothing ever checked again after they first connected.
That turned out to be a family, not an incident. Taking something away did not take. Demoting
an administrator left them with every admin power until they happened to sign out. Deleting an
account left its session working. Changing your password — the thing you do when you think someone
else is in your account — locked nobody out. A creature you built for your table could be read, and
permanently copied, by anyone running a campaign of their own. And an image uploaded under a name
ending in .html was handed back to browsers as a web page, from your instance's own address.
Those came out of a deliberate audit of the whole codebase rather than just the new code, which is
how the older ones had gone unnoticed for so long. Ten of the fixes below came from it, and all but
one were present in 1.3.0 and earlier.
Away from that: your rulebooks can live in CozyVTT now, the DM seat can be handed to someone
else without editing the database, and the rolls you keep retyping can be saved as buttons.
⚠️ This release migrates your database. It adds two new, empty tables — one for saved dice
rolls, one for documents shared with a campaign — and alters nothing that exists. Campaigns,
characters, maps and history carry over untouched. The backend applies it on start and refuses
to boot if it fails. There is no command to run afterwards this time. See
Updating your instance — take a dump first, and rebuild rather
than restart.
✨ What's new
Rulebooks and handouts, readable inside CozyVTT
(#39) A group's rulebook used to live on
everyone's own device, or on some other website with a link passed round. There is now a
Documents section on your dashboard, kept apart from the Asset Library so a rulebook never sits
among your map thumbnails. Upload a PDF, plain text or Markdown file, or write one from scratch
without uploading anything, and read it full-screen. Text and Markdown documents can be edited
afterwards, so house rules stay current without re-uploading.
Where you put it decides who can read it. A Personal document is yours alone until a DM shares
it with a campaign. A Campaign one belongs to that table. A Global one is readable by everyone,
and only an admin can make one. Inside a campaign, a book icon in the header opens what has been
shared — for every member, not just the DM.
Save the rolls you keep retyping
(#47) Some rolls are never on a character
sheet: a homebrew subsystem your table invented, a recurring 2d6+3 house rule, 4d6kh3 for rolling
up a new character. Press Saved beneath the dice buttons, give a roll a name and an expression,
and it becomes a button of its own. They are yours alone — not even your DM sees them — and they
belong to the campaign you made them in. An expression that cannot actually be rolled is refused
when you save it, with an explanation, instead of becoming a button that fails every time.
The DM can hand the game to someone else
(#33) A campaign has always had exactly one
DM and no way to change who it was. Open Campaign Settings → Members and click the crown beside
anyone at the table. They get the DM's controls; you become a player in the same campaign, keeping
your characters and your seat. It happens live — if you are both in the session, the controls
move across there and then.
Owning a campaign is separate from running it, and does not move. If you created it you still
own it afterwards, and an Owner Settings button keeps the two things still yours: deleting the
campaign, and taking the DM seat back.
Walls can be selected and moved
The Select tool could only take one wall and drag its endpoints, so there was no way to move a wall,
let alone a whole room. Click one, Shift-click to add more, drag a box over an area, or press Ctrl+A
for the lot; then drag them or nudge with the arrow keys, a whole grid square at a time with Shift
held. This is also the fix if a map's walls do not sit on its artwork — select them all and nudge
them into place.
Hit dice you can actually spend
(#43) A D&D 5e sheet has always had
somewhere to record hit dice, but the number was not clickable the way an ability score is. They can
now be spent from the sheet and from the right-click roll menu, and the remaining count goes down.
A roll of your own, from your own token
Right-clicking a creature has always offered the DM a Custom Roll box for anything its stat block
does not cover. Right-clicking your own character offered only what the sheet could work out, so
anything else meant leaving the token and typing it in the Dice tab. That box now sits at the foot of
your character's roll menu — and because it comes from there, the result is filed under your
character rather than just your name.
🐛 What's fixed
Access and revocation — all from the audit
- Removing someone from a campaign now takes effect straight away. A removed player, or one
whose role changed, kept playing on the connection they already had until they closed the tab - Taking away someone's access now takes effect at once. An administrator's powers were read
from their sign-in and never checked again, so removing admin rights, withdrawing permission to
manage shared assets, or deleting the account entirely changed nothing for them - Changing your password now signs out your other devices. If you thought someone else had got
into your account, changing your password did not remove them. Turning off two-factor does the
same now. The device you are using stays signed in - Homebrew creatures stay in the campaign they were made for. Anyone running a campaign of their
own could ask for another table's creature by its id, get the whole stat block, and keep a copy.
Those ids are not secret — every monster placed on a map carries one - Only the person who controls a token can drag it. While a token was mid-drag the server passed
its position along without checking who was dragging, so any player could take hold of the DM's
monster and send it skidding across everyone's map - Only you and your DM can roll your character's dice.
(#42) - Clearing the dice history follows who is running the game, not who originally created the
campaign
Files and what gets served back
- A failed restore no longer empties your database. A backup starts by deleting the tables it is
about to rewrite. If the file turned out to be incomplete or damaged, the deletion happened, the
replacement did not, and the screen said the restore had finished successfully. It is now all or
nothing, and says so honestly when it fails - An uploaded map or token image can no longer be served as a web page or a script. CozyVTT
checks an image by its contents, but stored it under whatever name was sent and served it back
with a type taken from that name - An uploaded audio file can no longer be served as a web page either
- An upload has to look like what it claims to be. Two exceptions had crept in where a filename
was trusted over the bytes - Downloading an asset follows the same rules as viewing it
- Updated the image library that makes previews. The version in use had known flaws in the code
that reads GIF images, reachable by anyone who could upload a token - Notes and documents no longer load pictures from other websites, which told that site who
opened the document - Instance branding images must live on your own instance
- The app now tells your browser what it is allowed to do — a content security policy on the
app page itself
Maps and UVTT import
- An imported map is no longer pitch black for your players. Importing switched dynamic lighting
on whenever a file brought walls, and most files bring walls without lights - Maps exported as part of a bigger map now line up. Some tools export a region and record where
it sits; CozyVTT was not reading that, so every wall landed offset - A UVTT whose walls reach outside its own picture now says so before importing
(#59) and lets you decide - Furniture can block sight now, if you want it to — tools like Dungeondraft keep furniture walls
separate, and CozyVTT ignored them completely - Coloured lights from a UVTT keep their colour
- A map too big to edit is refused when you import it rather than importing and then refusing
every edit - Maps imported from a UVTT get the same checks, and a preview, as any other upload
- The map tool panels start out of the way, and folding one puts its tool down — you could
arm Reveal, fold the panel, and then change what your players saw with nothing on screen explaining it
At the table
- Atmosphere audio from the DM's own library now plays for the whole table. The DM heard it and
nobody else did - The atmosphere track list only offers tracks that will actually play
- Chat history goes all the way back again.
(#44) Load More fetched the same fifty
messages over and over - **A roll now says who it is for, not jus...
v1.3.0
CozyVTT v1.3.0 — The Sheet Disagreed With Itself
Almost every character sheet had two halves that had stopped speaking to each other. The
editor let you record things the reader never displayed; the reader displayed things the editor
gave you no way to fill in. A Call of Cthulhu investigator could be shown as Dying with no box
anywhere to tick that said so. Pathfinder rituals, proficiencies, conditions and treasure could
all be typed in, and none of them ever came back.
Underneath a good number of those sat one defect worth naming. Every built-in starter template
had been written against an older version of the sheet, so each filled in fields nothing on the
page reads. A D&D 5e Fighter's Features tab was blank while "Second Wind" sat in the database with
its full rules text. A Pathfinder Fighter reported "No strikes recorded" while holding a warhammer.
The same template renamed your character the first time you saved. Every D&D 5e sheet announced
"Wizard Spellcasting" whatever the character was.
Away from the sheets: dynamic lighting now requires line of sight, the one change here worth
telling your table about beforehand. You get private notes per campaign, the session recaps
your DM has written since the feature shipped are finally readable, and the codebase is strictly
typed again — 326 uses of any gone, with CI running the checks on every push.
⚠️ This release migrates your database. It adds one new table for notes and alters nothing
that exists, so campaigns, characters, maps and history carry over untouched. The backend applies
it on start and refuses to boot if it fails. Pathfinder 2e tables have one command to run
afterwards. See Updating your instance — take a dump first.
✨ What's new
Notes of your own, in a new Notes tab
Up to two hundred per campaign, written in Markdown with a preview, saving themselves a moment
after you stop typing. They are private — nobody else can read them, including your DM, and
that is enforced on the server rather than hidden in the page.
Your own skills on a D&D 5e sheet
Tool proficiencies had nowhere to live, so players were recording Thieves' Tools as a weapon to
get something clickable — which put a lockpick on the combat tab with an attack roll it does not
have. Stats & Skills now has Your Own Skills: name it, pick its ability, tick proficiency
or expertise, and the bonus is worked out for you. It appears with the other skills and in the
right-click roll menu. Anything works, not just tools.
Past sessions are readable
CozyVTT always saved the Session Notes box and never showed it again. The Session tab now
lists every finished session with its date, length and recap.
⚠️ Notes written months ago appear immediately — they were there all along. Because nothing
ever displayed them, some DMs used that box as a private reminder to themselves, and those are
now visible to the table. The DM can edit or clear any past session's notes. Have a read through
before your players do.
Sheets can record what the rules ask for
The Call of Cthulhu sheet gains conditions, Cthulhu Mythos and spells, appearance and Keeper's
notes. Weapons can carry all eleven properties as buttons — plus any homebrew one you type — and
more than one damage roll, so a spear's 1d6 and 1d8 are both clickable instead of a sentence in
a note. Features and traits can carry their descriptions. Exhaustion is tracked in its six levels
rather than one tick box. Spell save DC and spell attack are derived, each with an Other bonus
box; if you had typed your own numbers, the difference is kept.
🐛 What's fixed
Character sheets
- Templates fill in the fields the sheet actually reads — features, proficiencies, languages,
traits, strikes. A test now compares each template against its schema. D&D 5e needs no migration;
Pathfinder 2e needs one command, below - Your character keeps the name you gave it. Picking a starter template replaced it, and the
first save renamed the character permanently - Characters no longer claim to be wizards — there is a spellcasting class field now, and a
character with no spells gets no spellcasting panel - Pathfinder AC, Class DC and Initiative are right. Initiative read +0 for everyone; the
built-in Fighter shipped AC and Class DC each a point too high - Pathfinder rituals, proficiencies, conditions and treasure appear on the sheet, and deity and
alignment can be recorded. A character with a ritual can be saved again - Adding a feature works, and a deleted one stays deleted
- Languages stay where you put them — unrecognised ones were filed under Weapons
- A sheet with an empty Hit Dice total opens again
- A strike with no proficiency no longer takes the sheet down to an error screen
- Hit point edits show up straight away on the roster and the map token
- The roll picker offers rolls your character actually has. Pathfinder skills were all offered
at +0, lore skills and every Call of Cthulhu characteristic and weapon roll were missing entirely.
Your sheets held the right numbers all along; the menu read the wrong places
Map and play
- A lit room behind a wall is no longer visible — or readable — through it.
(#29) A light was being treated as a
second pair of eyes. Worst of it, creatures in that room were sent to the player's browser, so
their positions could be read even where the map drew them hidden - Players can see the map and token art again. A picture from the DM's own library was never
shared with the campaign — maps showed "Failed to load", tokens silently fell back to a coloured
circle. Permission now follows use - You cannot lose your own token underneath someone else's. Creatures no longer share a square;
a creature at zero HP is drawn faded and can be stood on. The DM is exempt from both - Pointing at the dark no longer names what is standing in it
- A token joining initiative starts with no initiative rather than last fight's leftover roll
- A DM can mark an NPC with any of the fifteen conditions — three were missing
- The DM can put any player's character on the map, with or without a token picture
- A character created for a campaign actually appears in it
- A character's new picture reaches its token again
- Map notices are readable, at full width in the corner, with a working close button
Everything else
- One unreadable dice roll no longer takes down the whole campaign screen for everyone in it
- Your own secret rolls stay in your list, marked as secret, with a button to hide them
- Signing in successfully no longer counts towards the lockout — five correct logins in
fifteen minutes locked you out, shared across everyone behind one router - The setup wizard keeps the settings it asks you for. Instance name, timezone and
self-registration were collected, confirmed, and never saved — so an admin who turned
registration on got an instance with it off - Finishing setup signs you in and takes you to the dashboard, as it always promised
- The backup and restore scripts work on a Docker install.
backup.sh— what every upgrade
instruction tells you to run — failed withpg_dump: command not found - A large save no longer fails with an unexplained error. The limit was 100 KB, reported as "An
unexpected error occurred"; it is now 1 MB and says so plainly - Uploaded files are filed by what they are rather than all landing in the maps folder. Existing
files stay where they are and keep working
Security — a token's HP, size, conditions and stat block are now validated before storage,
matching what the token template route always did. Reachable only by a campaign's own DM, so this
is hardening rather than a hole anyone could walk through.
📖 Documentation
- The SRD attribution now says the right thing. SRD 5.1 is used under Creative Commons
Attribution 4.0, not the OGL, and Open5e is credited alongside - All 26 documents checked against the code — the permissions reference had been missing four
flags since v1.0.0, the schema checklist described 10 models where there are 17 - The WebSocket event list is now generated from the handlers, covering all 39 events the server
listens for and 36 it emits, so it cannot fall behind again - Twelve endpoints that no longer exist removed from
docs/API_REFERENCE.md, and eleven missing
ones added to the backend spec. These routes are not a public API and carry no compatibility
promise — but an earlier draft claimed no program could authenticate at all, which was wrong, and
has been corrected
⚠️ Behaviour changes
- Dynamic lighting requires line of sight. A lit room is only visible to someone who can
actually see into it. Tell your players - Creatures cannot share a square, except for the DM
- Secret rolls are labelled honestly. The checkbox said "only you can see"; the DM has always
been sent them deliberately. It now reads "hidden from other players". Who sees what has not
changed — only what you are told - Past session notes are visible to the whole campaign. Check yours before your next game
- Initiative on a token joining a fight starts empty
- "Reassign to Player" is gone from the roster menu — it only ever opened a "not yet available"
notice. It returns when it works
⬆️ Updating your instance
💡 Back up first. Migrations are forward-only, so rolling back to 1.2.2 means restoring a dump.
./backend/scripts/backup.sh
# From your CozyVTT directory
git fetch --tags
git checkout v1.3.0
docker c...v1.2.2
CozyVTT v1.2.2 — Saved, But Never Read Back
Nearly everything this release fixes was already being written down. Every dice roll was
saved to the database — twice, in fact. Every D&D 5e sheet had an Initiative box and a
passive Perception score on it. Every character had a name, typed when you created it. None
of it was ever read back. The dice panel started empty on every reload, initiative rolled a
flat 1d20 whatever the sheet said, passive Perception kept whatever number it was first
given, and a brand-new character opened showing "New Character".
Underneath several of those sat one defect worth naming on its own. Reconnecting to the
server replaces the underlying connection, and every live subscription in the app was quietly
left attached to the discarded one. Nothing re-subscribed. That is the Roll button that
sticks on "Rolling…" with no reproducible trigger — the trigger was a dropped connection,
which has no visible sign — and it is the same reason the initiative tracker freezes and
tokens stop moving on the map.
On top of that: players can roll their own initiative for the first time, by their game
system's actual rules rather than a d20 for everybody; conditions on the map are legible
instead of a row of ambiguous single letters; invitation links finally reach the page they
were always pointing at; and inviting a player to a campaign no longer emails them unless you
ask it to.
⚠️ This release migrates your database, as 1.2.1 did. It adds one nullable column and
touches no existing row — every roll you have already made stays visible. The backend
applies it on start and refuses to boot if it fails, so a broken upgrade is loud rather than
silent. See Updating your instance — take a dump first.
✨ What's new
Players can roll their own initiative
The die beside a combatant in the turn order was the DM's alone. Once the DM had added
everyone to the tracker there was no way for a player to roll for themselves — the DM rolled
for the whole table, or took numbers called out loud and typed them in.
A player now gets that same die, on their own row only, and can also right-click their token
on the map and pick Roll Initiative from the Roll... menu. The result goes straight
into the turn order and the roll appears in chat, attributed to the player who made it.
The option shows only once the DM has put that token in the tracker — rolling is how you take
part in a fight you are already in, not a way to add yourself to one. Everything else stays
with the DM: who is in the fight, the order, typing a value in by hand, whose turn it is, and
ending combat. The DM can still roll for anyone, players included.
Two deliberate limits on the player's die. It disappears once combat has started, because
re-rolling re-sorts the order and could skip somebody's turn — ask your DM to change a value
mid-fight. And spectators never get it, even on a token that was theirs before they were
demoted.
Who is actually at the table, shown as a dot instead of a wall of chat
Joining and leaving used to be announced in chat. Those notices were written every time a
connection was made or lost — so on every page load, every refresh and every brief network
blip — and each one was a permanent message rather than a passing notification. On a flaky
connection they buried the actual conversation.
Presence is now shown where it belongs: a green or grey dot beside each name in the Campaign
Roster, which is what the messages were trying to tell you in the first place. Someone with
two tabs open stays green until they close the last one, and moving to another campaign clears
them from the one they left.
Existing campaigns keep the messages they already have — nothing is deleted on upgrade —
and a DM who wants them gone can clear them with the eraser button on the chat panel.
Campaign invitation emails are opt-in
Inviting a player used to email them every time, with no way to say no. That is awkward if
you are sitting next to them, and worse if the address on file is stale.
The invite dialog now has an Also email them an invitation checkbox, unticked by default.
The invitation still appears on their dashboard either way — that is what they actually act
on — so nothing is lost by leaving the box alone. On an instance with no mail server
configured it explains itself and stays disabled rather than silently doing nothing. Campaign
invitations have never needed SMTP; only account invitations do, and the deployment guide
now says so.
🐛 What's fixed
The Roll button no longer sticks on "Rolling…" — and a reconnect no longer breaks the page
The cause was not in the dice code at all. Reconnecting to the server replaces the underlying
connection, and every live subscription in the app was left attached to the discarded one.
Nothing re-subscribed, so the dice panel stopped hearing about its own rolls — and since the
button is only released when the result comes back, it stayed disabled until the page was
reloaded. That is why nobody could pin down a trigger: the trigger was a dropped connection,
which has no visible sign.
Subscriptions now survive a reconnect, so chat, the roster and everything else keep working
after a blip too — including tokens moving on the map and the initiative tracker, which
subscribed in a way the central repair did not reach and were corrected alongside it. As a
backstop, a roll that goes unanswered for ten seconds releases the button and says so rather
than leaving you stuck.
One area is knowingly still outstanding and is logged on the roadmap: the map canvas's other
fifteen or so subscriptions — walls, fog, lights, the spirit layer, pings — still bind
directly and are lost on a reconnect. Nothing leaks; those events simply stop arriving until
the page is reloaded. It is a mechanical sweep left for its own change.
Initiative rolls now follow each game system's rules
Every initiative roll in the app was a flat 1d20 — no Dexterity, no bonuses, and the same
roll whatever you were playing, so a Dexterity 20 rogue rolled exactly what a Dexterity 8
wizard did. The server now works each combatant's initiative out from their sheet, so the
tracker's die and the map's Roll... menu always agree:
- D&D 5e rolls
d20 + your Dexterity modifier. Because Dexterity is not the whole story
— the Alert feat adds a flat +5, Jack of All Trades and Remarkable Athlete add part of your
proficiency bonus, some subclasses use another ability entirely — the sheet has a new
Initiative — other bonus box for everything else, and Initiative itself is now worked
out for you rather than typed in. Characters made before this release keep the total they
had: a hand-typed +7 on a Dexterity 14 character is read back as Dexterity +2 with an
other bonus of +5, so nothing changes under you, and it rolls that way immediately without
needing to be opened and re-saved. The one exception is a sheet whose Initiative was left at
0 — since it was typed by hand and blank sheets start at zero, that is read as "never
filled in" rather than as a real total, so such a character now correctly shows their
Dexterity modifier instead of nothing - Pathfinder 2e rolls
d20 +whichever stat the Uses: dropdown names — Perception by
default, or Stealth when you are sneaking up on someone. That dropdown and the bonus beside
it were already on the sheet, but nothing ever calculated the bonus, so every Pathfinder
character has been rolling +0 no matter how good their Perception was - Call of Cthulhu 7e does not roll for initiative at all — combatants act in DEX order,
highest first — so the menu offers Set Initiative rather than Roll, takes your
investigator's DEX, and nothing appears in the dice log. The app previously invented a
1d10 + DEX/5roll that is not a rule in the book. A readied firearm still acts at DEX + 50;
that is a property of the round rather than the investigator, so the Keeper sets it - Shadowrun 6e rolls the character's own initiative dice and base from the sheet, rather
than a d20 - NPCs with a D&D 5e stat block roll from their recorded Dexterity too. A token with
nothing to work from still rolls a plain d20 - Not handled yet: things that grant advantage on initiative, such as a Sentinel Shield.
Roll2d20kh1in the dice panel and have the DM type the value in
Roll history survives a refresh — and clearing it sticks
(#25) Every roll was already being
saved — to a dice table and the message log — but nothing ever read them back, so the dice
panel was seeded only by rolls that happened while you had the page open. Reloading,
navigating away and back, or dropping your connection all started you from an empty list.
History now loads from the server on open and re-syncs after a reconnect.
Secret rolls stay secret. The server decides what each person may see before anything is
sent, so a player gets public rolls plus their own hidden ones and a DM gets everything.
Refreshing never reveals a roll you were not already entitled to — the filtering happens in
the query rather than in the browser, so a hidden roll never goes over the wire at all.
The DM's Clear button used to tell every connected client to empty its list while changing
nothing on the server, so a reload brought everything straight back. It now records when the
history was cleared and serves only rolls newer than that. The rolls themselves are kept
rather than deleted — secret rolls are stored deliberately so a DM can audit them later, and
an accidental Clear should not destroy that. Ending a session does not clear history; only the
DM's Clear does.
One limit worth knowing:...
v1.2.1
CozyVTT v1.2.1 — Nobody Starts From a Blank Page
Somebody at your table has already built the sheet you need. The DM has a pregen ready for
the player who hasn't accepted their invite yet; the one person who actually knows
Pathfinder has a clean level-1 fighter sitting in their library. Until now none of that
could be handed over — characters belong to exactly one account, and the only starter
content was four presets baked into the source. This release adds character templates:
publish a sheet, and anyone on your instance can copy it into a character of their own.
It also fixes the area-of-effect tools. Aiming a cone or a line moved the point it came
from, so rotating one made its tip jump between a handful of spots — and half the time the
shape emerged from the wrong side of the square and cut straight back through the caster.
⚠️ This release migrates your database, unlike 1.2.0. The backend applies it on start
and refuses to boot if it fails, so a broken upgrade is loud rather than silent. It is
purely additive and touches no existing row. See
Updating your instance — take a dump first.
✨ What's new
Character templates — shareable starter sheets
A new Character Templates page sits on the dashboard beside Your Characters and Asset
Library. Every template on the instance is visible to everyone — that's the point — and you
can filter by game system, search by name, or narrow to just your own.
Click Use on any template and it becomes a character that belongs entirely to you.
Rename it, change anything, play it; the original is untouched and whoever published it has
no control over your copy.
There are four ways to publish one:
- New Template on the templates page, starting from the same blank sheet the
character-creation flow uses, so every required field is there - Save as Template on a character sheet you already have
- Create & Publish as Template while making a character, so one action does both instead
of building the sheet twice - Import a character JSON file — including one exported from a different CozyVTT
instance
That last route is worth dwelling on. A character export is a sheet plus a game system,
which is exactly what a template holds, so a sheet built on one server can be carried to
another and published there with no conversion at all. Export from wherever you built it,
bring the file, share it with the new group.
Templates work for every game system CozyVTT supports, and will work for any system added
later without further work: editing a template's sheet uses the same per-system editor a
character does, so a system that gains a field gains it in templates at the same moment.
A template editor permission for curating what others publish
Publishing is open to everyone, which means someone eventually needs to fix a typo in a
template they didn't write. Admins can grant a user the template editor permission from
the admin panel, exactly as Global Assets is granted — it lets them edit or delete anyone's
template.
Nobody holds it by default, including on upgrade; an admin has to grant it deliberately.
Revoking it takes effect on the very next request rather than lingering until the user logs
out, and nobody can grant it to themselves. Without it, you can still publish freely and
edit or delete anything you published.
Alt+click places an area template freely
Not every effect is measured from a caster. A wall of fire is a line you drop wherever you
like within range, not one that starts at your feet. Holding Alt while clicking
an area template skips the grid snapping and pins it exactly where you clicked, with the
shape turning about that point instead.
🐛 What's fixed
Cone and line templates now sweep around the square you pin them on
Aiming a template recomputed the point it came from on every frame, sorting the angle into
one of eight 45° sectors — so rotating a cone made its tip teleport between a handful of
fixed spots instead of turning smoothly. Worse, that snapping ignored which way the shape
pointed: it rounded to the nearest grid line, which is the same line whichever way you aim
along an axis. Exactly half of all directions — the entire left side of the circle — put the
origin on the opposite edge, so a cone aimed left came out of the square's right side and
cut back through the caster's own token. The line did the same.
The square you click is now a fixed pivot that aiming never moves, and the origin slides
continuously around that square's edge to follow your aim: the middle of an edge when you
aim along a row or column, the corner when you aim diagonally, always on the side you're
pointing at. Keep turning and the shape sweeps around your token, which is the whole
reason to have the tool. This also finishes the grid alignment started in 1.2.0, where the
cone still snapped to the nearest intersection and sat half a square off centre. The cone's
53° spread is unchanged.
A global asset manager can delete their own global assets again
The permission check loaded the flag only when the requester was not the asset's uploader,
but the rule that consults it also requires that they are — so in the one case the flag was
meant to permit, it was never actually read and the delete was refused. Anyone holding that
permission would have hit this on every attempt to remove their own global upload. Admins
were unaffected, which is likely why it went unnoticed. The asset routes also gain their
first end-to-end test coverage, pinning the whole delete matrix across all three scopes
rather than only the case that broke.
Esc closes the area template tool, as the guide has always said it does
Nothing was listening for it, so the only way out was to click the toolbar button again. Esc
now drops the placement, and a second press puts the tool away — the same two-stage escape
the wall tools use, and it yields to a half-drawn wall first so it never steals the key from
something more transient.
Aiming a template also no longer snaps back to pointing right when the cursor crosses onto
the tool panel that sits over the map; it holds the direction you left it at.
A token with no image no longer shows a heart icon
The token edit panel used a heart where the image should be, which read as "favourite" and
gave no hint that the avatar is a button at all. It now shows a dashed circle with an upload
icon, matching the empty image slot in the token template editor.
📖 Documentation
- Character templates are documented across the guides — browsing and copying in the
user guide, preparing sheets for players who haven't joined in the DM guide (the motivating
case), and the endpoints plus a permission matrix in the API reference. docs/DEPLOYMENT.mdgains a Per-User Permissions section. Global Assets has existed
for some time and was never described for operators, so both permissions are now documented
together: what each grants, that they default off including on upgrade, and that revoking
takes effect on the next request.- The measuring and area tools are documented for players. Neither the ruler nor the AoE
tool is restricted to the DM, but only the DM guide covered them. The player guide now has
its own section — including that a player's ruler measures from their own token rather than
a clicked point, which is the one place the two behave differently. docs/GAME_SYSTEMS.mddistinguishes the two things called "template." The hardcoded
starter presets and the new user-published templates share the word; the guide now separates
them, and notes that a new game system gets user templates for free provided its blank-sheet
factory works.- The roadmap is corrected — it still listed the global asset manager admin toggle as
outstanding long after it shipped — and merging the hardcoded presets into the template
library is logged as the natural follow-up.
⚠️ Behaviour changes
- This release migrates the database. One new table and one new permission column, both
additive. See below. - Cone and line templates aim differently. The origin now tracks your aim around the
pinned square instead of snapping to one of eight positions. Templates are previews, so
nothing stored changes. - Esc now does something in the area template tool where it previously did
nothing: first press drops the placement, second closes the tool. - A template's token image must be a global asset. Everyone who can see a template needs
to be able to load its picture, so a personal asset would show as broken for every reader
but its owner. The API says so plainly rather than storing an image that cannot load.
⬆️ Updating your instance
💡 Back up first — and this time it matters more than usual. Migrations here are
forward-only, so rolling back to 1.2.0 means restoring a dump../backend/scripts/backup.sh
# From your CozyVTT directory
git fetch --tags
git checkout v1.2.1
docker compose down
docker compose up -d --buildNo configuration changes, and no migration command to run by hand — the backend container
applies it on start. If it fails, the backend refuses to start rather than serving against a
half-updated database, so you'll know immediately instead of discovering it later.
What the migration does, and doesn't. It adds one table nothing previously read, and one
boolean column on User that defaults to false. It touches no existing row. Your characters,
campaigns, maps, creatures and assets are untouched, and nobody gains the template editor
permission on upgrade — an admin grants it per user afterwards. This was verified against a
restored copy of a real 1.2.0 database: i...
v1.2.0
CozyVTT v1.2.0 — Less Bookkeeping at the Table
Half of running a game is admin: working out what a creature's Perception bonus should be,
saying "no, the other goblin", re-covering the square of fog you didn't mean to reveal.
This release takes those jobs off you — creature saves and skills are derived from ability
scores and proficiency instead of typed one at a time, the token whose turn it is wears a
ring everyone can see, and Tab drops a labelled ping wherever your cursor is. It also stops
CozyVTT offering d20 maths to games that have no d20s.
Fully backward-compatible: no database migration, no configuration changes. Your existing
creatures keep their exact numbers — an SRD Goblin opens already showing Stealth as
expertise at its printed +6, and values the rules can't produce, like the Night Hag's,
are preserved as overrides. Fog was always stored one cell per grid square, so revealed
areas on existing maps carry over exactly as they were.
✨ What's new
Creature saving throws and skills are worked out for you
Instead of typing a number for each one, tick which saves and skills a creature is
proficient or expert in and CozyVTT derives the bonus from its ability score and
proficiency. A commoner with Wisdom 14 who is proficient in Perception gets +4 — +2
Wisdom, +2 proficiency — and expertise doubles the proficiency to +6. All six saves and
all eighteen skills are listed, so there is no longer a free-text field where a misspelling
silently created a skill called "perceptoin".
The proficiency bonus comes from Challenge Rating on the same scale a character's comes from
level, and is shown with its source ("From CR 1/4"); changing an ability score or the CR
updates every derived bonus at once. Homebrew is still possible: any row can be overridden
by hand, and an override that the creature's ability scores and CR cannot support is flagged
rather than silently accepted.
Ping a location on the map with Tab
Put the cursor where you mean and press Tab: a dot appears with rings radiating
out of it, in your own colour and labelled with your name, visible to everyone at the table
for about a second and a half. Every player gets a consistent colour automatically — there
is nothing to configure.
Tab only pings when the cursor is over the map and you aren't typing or tabbing between
controls, so keyboard navigation is unaffected. Pings draw above dynamic lighting, so
pointing into an unlit area works, and repeat pings are rate-limited server-side. Under the
operating system's reduce motion setting the rings hold still and simply fade.
The acting combatant's token is highlighted on the map
During initiative, the token whose turn it is gets a pulsing gold ring, visible to everyone
at the table — so it's obvious which of five identical goblins is up, without counting rows
in the tracker. The ring is a gold band edged in black rather than a single colour, so it
stays legible over any map image, light or dark.
It respects the same visibility rules as the token itself: a creature hidden from players,
or standing in unexplored fog, shows no ring on their screens, so an ambusher's position is
never given away by their turn coming around. The reduce motion setting turns off the
pulse and keeps the ring.
Hovering an initiative row highlights that token, and vice versa
Pointing at a name in the turn order draws a thin white outline around its token and lifts
it slightly — quieter than the turn ring, and both can show at once. Hovering a token on the
map tints its row in the tracker the same way. It works for players as well as the DM, is
purely a pointer (it never selects or moves anything), and respects the same visibility
rules, so hovering a hidden creature's row doesn't give its position away.
🎲 Your game system, not D&D's
Creature rolls now depend on the system you're playing
The right-click NPC roll menu applied D&D 5e maths to every campaign, so a Call of Cthulhu
NPC was offered 1d20 + ability modifier for a percentile game that has neither d20s nor
ability modifiers, and a Shadowrun NPC the same for a dice-pool game. D&D 5e and Pathfinder
2e now each get their own correct treatment; Call of Cthulhu and Shadowrun offer the
free-form Custom Roll input instead of confidently wrong dice, and are noted for a
future release.
Pathfinder 2e creatures use Pathfinder's own structure
PF2e stat blocks print final modifiers because creatures are built from level benchmark
tables, not from proficiency ranks — so nothing is derived for them, unlike D&D 5e. PF2e
creatures now show Fortitude, Reflex and Will rather than six ability saves, a Level
rather than a Challenge Rating, and attribute modifiers rather than scores. Values are
entered directly and never recalculated; a number far outside the usual range for the
creature's level is flagged as a possible typo, nothing more.
The Creature Library shows your campaign's system by default
Every campaign saw all ~320 D&D 5e SRD creatures regardless of system, so a Call of Cthulhu
table browsed a list of dragons to find its own creatures. The library now defaults to the
campaign's system, with an All game systems option in Filters for deliberately borrowing
a stat block from elsewhere — worth keeping, since only D&D 5e ships SRD content. Creatures
saved without a system recorded always appear either way, and the seeding button is now
labelled Import D&D 5e SRD so it's clear what it fetches in a non-D&D campaign.
🐛 What's fixed
Editing a creature no longer deletes its saving throws and skills
The creature editor rebuilt the stat block from the fields on screen, and it had no fields
for saves or skills — so duplicating an SRD Goblin and renaming it silently removed its
Stealth +6, which also removed the skill from the right-click roll menu, with nothing to
indicate anything had been lost. The same save also dropped XP and notes. Every field the
form doesn't show is now carried through untouched.
A creature can no longer be given an impossible saving throw
The creature endpoints accepted whatever they were sent — the only checks were that the name
was a string and the stat block was an object — so a commoner could be stored with a +30
Wisdom save and the roll menu would faithfully roll 1d20+30. Stat blocks are now validated
on every write, on creature templates, token templates and campaign import alike.
Fog of war is a box selection instead of a brush
Drag over the map and the selection snaps to whole grid squares, showing its size (4 × 7)
as you go, so you reveal exactly the area you meant. The circular brush it replaces caught
neighbouring squares by accident, which on a fog tool means showing players a room they
weren't supposed to see yet. Click a single square to toggle just that one, drag in any
direction, and cancel a drag with Esc or a right-drag.
AoE templates now line up with the grid
Cube, cone and line were anchored to the centre of the hovered square, so they sat half a
square off — a 10 ft cube on a 5 ft grid straddled four squares instead of covering two.
Each shape now snaps by the rule that puts its edges on grid lines: an even span centres on
a grid intersection, an odd one on a square centre. A cube is also axis-aligned now rather
than rotating toward the cursor, since a tilted square can't align to a square grid. A
cone's point lands on an intersection, though its spreading edges still cross squares —
that's the shape itself. Circle is unchanged, as it was already placed correctly.
Smaller fixes
- Editing one creature straight after another no longer carries the first one's data
across. The open editor was reused without reloading, so the second creature showed the
previous one's name, ability scores and stats — and saving wrote them onto it. The same
applied to token templates. - The initiative tracker no longer freezes after a WebSocket reconnect. Its listener was
attached to a socket instance that gets rebuilt on reconnect, so after a dropped
connection the tracker silently kept showing whatever turn was current when the link went
down. Combat state is now re-synced from the server each time the socket comes back. - Creature token images can be chosen from the asset library, not just uploaded. Editing
a custom or duplicated creature previously offered only Upload, so an image you had
already uploaded couldn't be reused. There is now a Browse Assets grid with search
alongside Upload New. - Creature token images were broken even when uploaded — the editor's preview pointed at
/api/assets/{id}/file, an endpoint that has never existed, so the thumbnail silently
404'd. The token template library had the same defect. Both now use the real serving route. - Changing a token's image updates the map immediately for everyone. The canvas cached
token images by token id alone, so a changed image kept rendering the old picture until a
page reload — for every player, not just the DM. - Negative bonuses no longer display as
+-1, and multi-word skills read as "Sleight of
Hand" rather than "SleightOfHand". - The NPC Quick Editor's close button is no longer pushed off the panel edge by a long token
name, and the token avatar in its header is larger and easier to see.
📖 Documentation
docs/GAME_SYSTEMS.mdnow covers creatures, which it had never mentioned — it
documented only the player-character pipeline, leaving the entirely separate creature model
undocumented. It adds the sharedNpcStatBlockshape, the four places creature code
branches on game system, and the decision a contributor has to make first: whether their
system's creature values are derived (D&D 5e) or *printe...