Atlas VTT

Connect a character sheet to Atlas

Send named dice rolls from your own character sheet service to the Atlas player page.

Your sheet can send a dice formula and a feature name to an open Atlas player tab. Atlas sends the request through that player's connection to the GM, who rolls the dice.

The sheet service can use any game system. You do not need approval from Atlas to add a new service name.

Use the Chrome starter

The starter extension contains plain JavaScript. It works in Chrome and Arc without a build tool. You can copy and change it under the MIT license.

  1. Download and extract the starter.
  2. Replace https://sheets.example.com in manifest.json and background.js with your sheet's origin. An origin is the protocol and host, such as https://sheets.your-game.com. Keep /* at the end of the manifest match.
  3. Change my-sheet in sheet.js to a short name for your service.
  4. Add an Atlas button to your sheet:
<button
  data-atlas-character="Mira"
  data-atlas-name="Spear damage"
  data-atlas-roll="d8+3"
>
  Roll in Atlas
</button>

Set these values from the current character. Include all bonuses in the formula. If you cannot change the sheet's HTML, change sheet.js to read its controls and add an Atlas button.

  1. Open chrome://extensions, or arc://extensions in Arc.
  2. Turn on Developer mode.
  3. Click Load unpacked and choose the extracted folder.
  4. Reload your sheet tab and your Atlas player tab.
  5. Join an Atlas table where the GM is connected and a scene is showing.
  6. Click Roll in Atlas on the sheet.

The starter finds one open Atlas player tab. Players do not enter a table code or choose a table in the extension. If several Atlas tabs are open, the starter asks the player to close the extra tabs before it sends a roll.

The table log shows Mira · Spear damage. The roll uses the player's acting token and the roll visibility set in Atlas. The sheet's character name is a label. It does not select a token or grant control of one.

How the browser connection works

An extension connects the two tabs through Chrome messages. Its content script is code that runs inside a web page. The content script in the Atlas tab sends a message to the player page with window.postMessage.

Sheet button → extension → Atlas player tab → GM

The starter has three scripts:

FileJob
sheet.jsRead the clicked feature and send its roll. Show the reply on the sheet.
background.jsCheck the sheet origin and find the Atlas tab.
atlas.jsSend a request inside the Atlas tab and wait for its reply.

You can use this bridge from your own extension. You can also use a userscript that can send messages between tabs. A normal page script on another website cannot send a message into an unrelated Atlas tab.

The bridge accepts messages from the Atlas window itself, with that window's exact origin. Messages from other origins, popups and frames are ignored. Keep this check in your integration. Use the exact origin as the second postMessage argument, as the browser API requires for safe delivery.

Check that Atlas is ready

Send this from a content script inside the Atlas player tab:

const id = crypto.randomUUID();
window.postMessage({
  channel: 'atlas-sheet-rolls/v1',
  kind: 'probe',
  id,
}, window.location.origin);

Atlas posts a reply into the same window:

{
  "channel": "atlas-sheet-rolls/v1",
  "kind": "reply",
  "id": "the-same-request-id",
  "reply": {
    "ok": true,
    "available": true,
    "capabilities": {
      "version": 1,
      "maxDice": 100,
      "maxFormulaLength": 100,
      "maxCharacterLength": 60,
      "maxNameLength": 200,
      "maxSiteLength": 64,
      "maxRollsPerSecond": 10,
      "deduplicationEntries": 128
    }
  }
}

available: true means the player can send a roll now. If it is false, ask the player to join a table with a connected GM and a visible scene. Check again when the player wants to roll, since the connection can change.

Match replies by channel, kind and id. Also check event.source === window and event.origin === window.location.origin. Start the reply listener before you send the request. Remove it when a reply arrives or the wait ends. The starter's atlas.js contains this code.

The channel name fixes the API version at 1. Clients must ignore extra reply fields. Older player pages can reply without capabilities or error codes. A page that has no bridge sends no reply.

Send a named roll

Use a new request ID for each user action:

window.postMessage({
  channel: 'atlas-sheet-rolls/v1',
  kind: 'roll',
  id: crypto.randomUUID(),
  roll: {
    site: 'my-sheet',
    character: 'Mira',
    name: 'Spear damage',
    formula: 'd8+3',
  },
}, window.location.origin);
FieldAccepted value
channelExactly atlas-sheet-rolls/v1.
kindprobe or roll.
id16 to 64 lowercase letters, digits or hyphens. Use crypto.randomUUID().
roll.site1 to 64 ASCII characters. Start with a lowercase letter. Use lowercase letters and digits, with single hyphens between groups. Example: my-sheet-2.
roll.characterA visible character name, up to 60 Unicode characters after cleaning.
roll.nameThe feature name, up to 200 Unicode characters after cleaning.
roll.setAppearanceOptional. Send true to use the GM’s names and colours for an explicit matching default pool.
roll.formulaA dice formula or one whole-number modifier, up to 100 characters after spaces are removed.

All four roll fields are required. The site name identifies your service and needs no registration. Use a short slug, such as my-sheet, rather than a URL.

Atlas removes control and text-direction characters from names. It joins repeated spaces and rejects empty names. Raw character names over 240 JavaScript string units and raw feature names over 800 units are rejected before cleaning.

The displayed label has a limit of 120 characters. Atlas keeps up to 90 characters of the feature name, then fits the character name into the remaining space.

Choose the formula

Atlas accepts sums and differences of dice and whole numbers. Send explicit dice, or send one whole-number modifier to use the GM’s default dice. A modifier can start with a plus or minus sign.

FormulaMeaning
+2Roll the GM’s default dice and add 2.
-1Roll the GM’s default dice and subtract 1.
0Roll the GM’s default dice with no bonus.
d20+5Roll a d20 and add 5.
2d6+d4-2Roll two d6 and a d4, then subtract 2.
2d20kh1+5Keep the highest d20 and add 5.
2d20kl1+5Keep the lowest d20 and add 5.
d12+d12+2Roll two d12 and add 2.
d%Roll percentile dice.
2d6!!Allow each d6 to explode once on its highest face.
2d6!iAllow repeated explosions, within Atlas's dice limits.

Use at most 100 starting dice across the whole formula. A die can have 1 to 1,000 faces. A keep count must be at least 1 and no larger than its dice count. A fixed number can contain up to eight digits. The raw formula must be no longer than 400 characters.

Atlas accepts uppercase dice letters and normal spaces, then normalizes the formula. It also accepts the Unicode minus sign. Do not put spaces inside a number or between d and its face count.

A modifier alone can contain up to four digits. Atlas turns 2 into +2. Do not send a sum with no dice, such as 2+3.

For a modifier, Atlas uses the assigned character sheet’s dice set first. If the sheet has no override, Atlas uses the played campaign’s settings, or the collection’s settings when the campaign uses the collection’s system. Default dice keep the names and colours the GM chose. Atlas reads the settings when the roll is made. This works for public and private rolls.

Explicit dice always win. For example, 1d8+2 stays a d8 roll even if the GM’s default is two d12s. Explicit dice take no default names or colours unless you add setAppearance: true. Send +2 if you want the default dice. Do not add a d20 to that modifier. Your service does not need a built-in game preset.

To use the GM's names and colours for an explicit default pool, add setAppearance: true:

roll: {
  site: 'your-sheet',
  character: 'Mira',
  name: 'Check',
  formula: 'd12+d12+d6+2',
  setAppearance: true,
}

Atlas checks the first dice against the GM's whole default pool, in order. If that pool has two named d12s, the two d12s get those names and colours. The extra d6 stays plain. 2d12 can also match two separately named dice. If a base die is missing, has another size or is negative, Atlas keeps the normal appearance.

This field changes appearance only. The dice and bonus stay as written. The GM still selects the dice set: character first, then campaign or collection. Do not send colours, die names or dice-set IDs. Older player pages and GMs ignore this optional field. Atlas Dice sets it for Demiplane's duality pool. It leaves ordinary damage rolls alone.

Resolve your sheet's variables before sending. For example, send d20+5 when an attack uses a +5 bonus. Formulas such as d20+proficiency, 2d6*2, or d20+ are refused.

Send the formula when the user clicks. Keep the Atlas action separate from the sheet's native roll, so one click makes one roll. Your service controls resource use, such as spending a spell slot or Hope.

Read success and failure

A roll request that was sent to the GM receives:

{ "ok": true, "available": true }

This confirms dispatch to the GM. The GM still checks the request and makes the result. Read the result in Atlas, where the normal dice display and table log show it.

A refused request receives an error inside the reply envelope:

{
  "ok": false,
  "code": "invalid-roll",
  "error": "This sheet roll is missing a name or has an unsupported formula."
}
Bridge error codeAction
invalid-rollFix the names, service slug or formula.
not-readyAsk the player to check the GM connection and scene in Atlas.
send-failedThe request was not queued. Restore the connection before a new roll.
rate-limitedWait at least one second before a new user action.
delivery-uncertainCheck the Atlas log before making another roll.

Show the error text when a client does not know a code. The starter also reports tab and browser failures, such as no-table, ambiguous-table, no-bridge and untrusted-source.

Wait up to three seconds for a reply, as the starter does. If a roll times out, its delivery is uncertain. Never repeat it automatically. A lost reply does not mean the GM missed the roll.

Atlas keeps the replies for the last 128 unique roll IDs in that player page. Repeating an ID returns its cached reply, even if the new payload is different. Cached requests do not roll again or use the rate allowance. This protection ends when the page reloads or the reply leaves the cache.

Atlas allows ten new roll requests in a one-second window. Invalid requests also use this allowance. Probes and cached requests do not. Refused rolls stay refused under the same ID, even if the table later becomes ready.

Keep player access in Atlas

Send only the service name, character label, feature label, formula and optional appearance flag. The bridge returns status and errors. It does not return the player's key, account data, table code or a dice result.

The player's Atlas connection determines who sends the roll. The GM checks token control and the normal table rules. A service name or character label is text from a sheet, not proof of identity.

Keep your extension's permissions limited to your sheet and the Atlas player site. Do not add arbitrary cross-origin message access to the player page. Scripts that already run inside the Atlas page can use the bridge, so treat all roll fields as untrusted input.

Test your service

  • Change a character bonus, then roll. Check that Atlas uses the new value.
  • Check the feature name, character label and acting token in the table log.
  • Open a second Atlas tab. Check that your extension asks for a destination or refuses the roll.
  • Disconnect the GM. Check that your sheet shows an error and does not queue a roll.
  • Block a reply after sending a roll. Check that your extension shows uncertain delivery and does not repeat it.
  • Send an invalid formula and a repeated request ID. Check that they cannot make an extra roll.

The starter instructions include the install steps. See Chrome's content script documentation when you adapt it to a sheet that changes without reloading.