I figured it was time to get a thread going on the new Custom Roll Parsing and see what people have come up with so far. The examples below are probably not beginner-friendly, but the posts are already long enough without going into the workings of setAttrs, getAttrs and so forth - they've been omitted from the code with the assumption that anyone interested in putting startRoll() through its paces will know how to use the basic sheetworkers. The new startRoll() function, combined with action buttons, offers a bunch of new functionality to explore, as a roll sent to chat can now have custom functions run before it, during it, after it, and even passed through to the next roll. The startRoll() function itself is the main meat of the recent-ish Custom Roll Parsing update, and it's also the first time Roll20 have exposed a Promisified, async/await-ready function for sheet authors. The async/await pattern is great for keeping code tidy & controlled by avoiding callback hell. To really take advantage of startRoll's promisified version, we need our other major sheetworkers to return Promises. Luckily, OnyxRing got this figured out already - here's the original thread . I've been using a condensed version, but it's functionally the same as OR's original, and takes the same parameters as Roll20's base functions (obviously): const asw = (() => { const setActiveCharacterId = function ( charId ){ let oldAcid = getActiveCharacterId (); let ev = new CustomEvent ( "message" ); ev . data ={ "id" : "0" , "type" : "setActiveCharacter" , "data" : charId }; self . dispatchEvent ( ev ); return oldAcid ; }; const promisifyWorker = ( worker , parameters ) => { let acid = getActiveCharacterId (); let prevAcid = null ; return new Promise (( res , rej ) => { prevAcid = setActiveCharacterId ( acid ); try { if ( worker === 0 ) getAttrs ( parameters [ 0 ]||[],( v ) => res ( v )); else if ( worker === 1 ) setAttrs ( parameters [ 0 ]||{}, parameters [ 1 ]||{},( v ) => res ( v )); else if ( worker === 2 ) getSectionIDs ( parameters [ 0 ]|| '' ,( v ) => res ( v )); } catch ( err ) { rej ( console . error ( err ))} }). finally (() => setActiveCharacterId ( prevAcid )); } return { getAttrs ( attrArray ) { return promisifyWorker ( 0 , [ attrArray ])}, setAttrs ( attrObj , options ) { return promisifyWorker ( 1 , [ attrObj , options ])}, getSectionIDs ( section ) { return promisifyWorker ( 2 , [ section ])}, setActiveCharacterId , } })(); Your getAttrs call would then look like this: let attrs = await asw.getAttrs(['attr1', 'attr2']); That's it. As long as you don't forget the word 'await', you can now access those attributes with attrs.attr1, attrs.attr2, and so on. I won't dwell on the async coding part - but all the examples I provide use the above framework. Synchronous versions of all the examples are certainly possible, but.... yeah, probably not alot of fun if you're dealing with a complex, multi-part roll function. We can also assume that each of these functions is running from a type="action" button and its associated event handler, e.g. <button type="action" name="act_test">Uninstall Windows</button> on('clicked:test', (ev) => testFunction(ev)); So, provided you have the usual requirements - minimal HTML to provide some test buttons, a legal script block, and an event listener to attach each main function to your HTML test button - all of the following examples should work copy & pasted into a sheet. They're not terribly useful by themselves, but you should be able to see them working. Also, something I find generally helpful for reading posts with code on Roll20, a quick Stylus addition: .postcontent pre { white-space:pre-wrap!important; } .postcontent div { white-space:pre-wrap; word-break: break-word; } This should wrap code-boxes, and force VS Code lines to wrap when they hit the edge of the black background. 1. Let's Go Fishing - pre-roll data grab Trick number 1 is pretty straight-forward. Sometimes you might want to fish for some data before starting your roll. A couple of examples: 1. You might want to set up your roll template based on input from the Player. Maybe you want to Query the player on which weapon to use for the attack? 2. You could put a generic button on your damage template, so anyone can click it to apply the damage to their selected token. All we do is send an API roll to chat (assuming you don't want it to be visible) and use startRoll's functionality to pinch the data from the roll object: // Weapon rep sec suffixes const weaponAttributes = [ 'name' , 'damage' , 'damagetype' ]; // Helper function to grab player input const getQuery = async ( queryText ) => { const rxGrab = / ^ 0 \[ ( . * ) \] \s * $ / ; let rollBase = `! {{query1=[[ 0[ ${ queryText } ] ]]}}` , // just a [[0]] roll with an inline tag queryRoll = await startRoll ( rollBase ), queryResponse = ( queryRoll . results . query1 . expression . match ( rxGrab ) || [])[ 1 ]; finishRoll ( queryRoll . rollId ); // you can just let this time out if you want - we're done with it return queryResponse ; }; // Fake function - the real one would asw.getAttrs() the repeating section and get the rowIds and names const getWeaponIds = async () => { return `weaponName1,weaponId1|weaponName2,weaponId2` ; } const myRoll = async () => { let weaponData = await getWeaponIds (), readyWeapon = await getQuery ( `?{Which weapon?| ${ weaponData } }` ), weapRow = `repeating_weapon_ ${ readyWeapon } ` ; // convenience string for the required row // The player has chosen their Weapon by name, and getQuery has returned the rowId we need // Run the usual transform on your attribute list to get the attributes needed for the roll let requiredAttrs = weaponAttributes . map ( a => ` ${ weapRow } _ ${ a } ` ), weapAttrs = await asw . getAttrs ( requiredAttrs ), rollBase = `&{template:default} {{name=Attrs list}} {{= ${ requiredAttrs . join ( ' \n ' ) } }}` ; let attackRoll = await startRoll ( rollBase ); // Proceed with the roll, and whatever calculations need doing finishRoll ( attackRoll . rollId ); } Not a terribly amazing example as it's so simple - you could achieve the outcome with a simple roll button, but hopefully it gets across the concept of how to pinch text input from a roll. A second example, making better use of our new functions. Let's say we've got a "Heal Target" action button on the character sheet - we can now coax the heal roll to actually apply the HP to the target. This is probably not ideal sheet design, allowing a player to adjust the HP on another sheet - a better approach would probably be to whisper a second button to the target allowing them to accept the heal and apply the adjustment. But we'll stick with the automatic approach for this example. Like before, we use a function to grab the data we need with an invisible API roll, then use that info for our actual roll. The difference here is we use OnyxRing's clever function to switch the active character in the sandbox so we can apply the HP change to the target: // Helper - like getQuery() above, but fishes for a target click const getTarget = async () => { const rxGrab = / ^ 0 \[ ( . * ) \] \s * $ / ; let rollBase = `! {{charname=[[0[@{target|h1|character_name}] ]]}} {{charid=[[0[@{target|h1|character_id}] ]]}}}}` , targetRoll = await startRoll ( rollBase ), target = { name : targetRoll . results . charname . expression . match ( rxGrab )[ 1 ], id : targetRoll . results . charid . expression . match ( rxGrab )[ 1 ], }; finishRoll ( targetRoll . rollId ); return target ; } // Main roll function const healTarget = async () => { let target = await getTarget (), healerAttrs = await asw . getAttrs ([ 'heal_bonus' , 'character_name' ]); let rollBase = `&{template:default} {{name=Heal}} {{Heal= ${ healerAttrs . character_name } heals ${ target . name } for [[2d6 + ${ healerAttrs . heal_bonus || 0 } ]]}}` ; let healRoll = await startRoll ( rollBase ), healAmount = healRoll . results . Heal . result ; // Now we switch the active character in the Sandbox using OnyxRing's function console . log ( `Switching ID in sandbox to ${ target . id } ...` ); asw . setActiveCharacterId ( target . id ); let targetAttrs = await asw . getAttrs ([ 'stamina' , 'stamina_max' ]), finalHp = Math . min ( parseInt ( targetAttrs . stamina ) + healAmount , parseInt ( targetAttrs . stamina_max )); setAttrs ({ stamina : finalHp }); finishRoll ( healRoll . rollId ); }