Because of the differences between the legacy sheet infrastructure and the Beacon sheet infrastructure, not every existing Mod Script works with Beacon sheets out of the box. The notes below will help you update scripts so they work with Beacon sheets (for example D&D 2024). We have also updated several core scripts so there are examples and ready-to-use scripts for your games. Scripts that have been updated:
- Group Initiative
- TokenMod
- Group Check
- StatusInfo
For many scripts, making them compatible with the 2024 sheet comes down to two changes: how you get and set attributes, and how you parse roll templates / chat messages. This document walks through both, and also covers common problems so you can update a script to work with both the D&D 2014 sheet and the D&D 2024 sheet.
Beacon computed properties and user.* custom attributes require Mod Script Sandbox v1.5 (Campaign().sandboxVersion === "1.5"). v1.5 is the current default sandbox. getSheetItem and setSheetItem exist on both v1.0 and v1.5. On v1.0 they fall back to the legacy attribute get/set functions, so games without a Beacon sheet still work. On v1.5 they also read and write Beacon computed properties and user.* fields. Mod Script Sandbox v1.5 only. getComputed, setComputed, and performAction (see Mod Scripts: Function Documentation).
If a game’s sandbox dropdown still offers Default vs Experimental labels, confirm the running sandbox with Campaign().sandboxVersion rather than the label. Beacon computed properties need "1.5".
Updating get/set
The major change between accessing data for the 2014 sheet and the 2024 sheet, code-wise, is how you get and set attributes. There is now a set of asynchronous functions called getSheetItem and setSheetItem. Here’s an example of using the new functions:
const getDeathSaveSuccess = async (id) => {
const firstSuccess = await getSheetItem(characterId, "deathsave_succ1");
log(`First success is ${firstSuccess}`);
}
If you’d like to get the maximum value of an attribute (if a maximum exists), you can pass in the property max, like getSheetItem(characterId, "deathsave_succ1", "max");.
You’ll notice in the code above that getDeathSaveSuccess is marked as async. All functions that use getSheetItem should use this async/await pattern or use promises. Here’s the same function rewritten as a promise:
const getDeathSaveSuccess = (id) => {
getSheetItem(characterId, "deathsave_succ1").then((firstSuccess) => {
log(`First success is ${firstSuccess}`);
});
}
If you’re trying to get several values at once (or one after another) and the rest of your code depends on that data, you can await each value individually or use Promise.all to resolve all of the promises at once and get the final values. If you don’t, the value you receive will be a pending promise, not the actual attribute value.
const getSuccesses = (id) => {
const promises = [];
promises.push(getSheetItem(characterId, "deathsave_succ1"));
promises.push(getSheetItem(characterId, "deathsave_succ2"));
promises.push(getSheetItem(characterId, "deathsave_succ3"));
Promise.all(promises).then((results) => {
log(`The first success is ${results[0]}, the second success is ${results[1]}, the third success is ${results[2]}`);
});
}
Asynchronous code can have several implications for how you write a script, depending on how you structured it. For example, if a script currently uses getAttrByName inside a replace or map, it will need to be split into a more async-friendly loop, because those functions will not wait for a value to return before continuing.
Let’s go back to “If you’re trying to get several values at once or one after another and the rest of your code depends on that data.” The rest of your code does not always depend on that value. Most of the time it will if you are using getSheetItem, because you want to do something with the attribute you are getting. For the inverse, setSheetItem, you often do not need to wait for it to finish. In that case you can ignore the async implications and just call it normally. The attribute will update in the background while your script continues to run.
The setSheetItem function works the same as getSheetItem, but includes an extra argument for the value to set:
setSheetItem(characterId, "hp", 10);
setSheetItem(characterId, "hp", 20, "max");
Updating roll parsing
Another thing many 5e scripts do that needs an update is parsing rolls. Rolls sent to chat are formatted differently and need to be parsed differently to get results or details about the content. The development team added some data attributes to the HTML that reduce the need for extensive HTML parsing. If you need more complex data, you may still need to parse it from the message sent to chat. A few common needs are below.
To get the result of a roll in the standard roll template:
const rollResultMatch = msg.content.match(/data-result="(.+?)"/);
To check what type of roll it is based on the title:
const deathSaveMatch = msgContent.match(/header__title">Enter the header here<\/div>/);
To check the roll subtitle and find information such as spell level or damage type:
const spellLevelMatch = msgContent.match(/header__subtitle">Level (.+?) /);
Because the 2024 sheet is still in active development, roll templates may change and require further script updates. We cannot guarantee that string-parsing the HTML will stay stable forever, but we are working toward more standardized templates as the sheet develops. The examples above are a bit rigid in their regular expressions for simplicity; we recommend looser matching and wildcards to make your matching more robust while the templates are still in flux.
Common problems
Error: No attribute or sheet field found for character_id (YOUR ID HERE) named (YOUR ATTRIBUTE HERE)
Likely cause: You are on Mod Script Sandbox v1.0 instead of v1.5 and are trying to access a Beacon computed property. Confirm Campaign().sandboxVersion is "1.5". If a sandbox dropdown is still labeled Default vs Experimental, the label can be stale; restart and check sandboxVersion (and the restart log) rather than trusting the dropdown alone.
Result of getSheetItem is logging an empty object instead of a value
Likely cause: not awaiting or using .then on the getSheetItem function. You have to wait for the value to return before moving forward with the code.