I think this one's manageable for you, just need to break it down a little bit. Other people might have other recommendations for using another IDE but I'm just using vsCode (I'm a beginner hobbyist though, not a Pro). Getting an IDE set up properly was more confusing for me than learning coding, to be honest. If you do grab vsCode, make sure you also install ESlint - the base linting for javascript is really sub-par, and ESlint really should be packaged with vsCode as standard. Anyway, on to your script. So there's some useful threads around the place, mostly with Aaron's beardy mug showing up somewhere. Here's a couple of threads from someone starting out on API scripting, you might find them helpful: <a href="https://app.roll20.net/forum/permalink/6605115/" rel="nofollow">https://app.roll20.net/forum/permalink/6605115/</a> <a href="https://app.roll20.net/forum/permalink/6584105/" rel="nofollow">https://app.roll20.net/forum/permalink/6584105/</a> One of them also has Aaron's API script boilerplate posted in it. This is an outdated one, but it'll suit our purposes. Unless you have something advanced in mind, you can pretty much always follow this structure: const NAME = (() => { // eslint-disable-line no-unused-vars const version = '0.1.0' ; const lastUpdate = 1531675536 ; const schemaVersion = 0.1 ; const checkInstall = () => { log ( '-=> NAME v' + version + ' <=- [' +( new Date ( lastUpdate * 1000 ))+ ']' ); if ( ! state . hasOwnProperty ( 'NAME' ) || state . NAME . version !== schemaVersion ) { log ( ` > Updating Schema to v ${ schemaVersion } <` ); state . NAME = { version : schemaVersion }; } }; const handleInput = ( msg ) => { if ( msg . type !== "api" ) { return ; } let args = msg . content . split ( /\s + / ); switch ( args [ 0 ]) { case '!NAME' : break ; } }; const registerEventHandlers = () => { on ( 'chat:message' , handleInput ); }; on ( 'ready' , () => { checkInstall (); registerEventHandlers (); }); return { // Public interface here }; })(); One of the threads above has a thorough breakdown on what each part of the script does, but basically: checkInstall() - This is for versioning your script, and for setting initial configuration on the {state} object. This is just an API-facing object which persists between sandbox restarts. The primary way of holding config info for scripts. We'll ignore this step for now. handleInput() - This is generally the primary event handler in a script, and reacts to a chat message being posted to chat. registerEventHandlers() - this is mostly for readability on larger scripts, an index of trigger points into your internal functions. Not strictly necessary, especially on smaller scripts. on('ready', ...) - this event is fired by the API when it finishes loading all the resources it needs. You can put code before this event, but unless you have a specific reason to, you generally want all your code to be behind this event. This is why Aaron has the registerEventHandlers() function gated behind the on('ready') ... it ensures none of the entry points to the internal functions are live until the API has finished loading. return {} - the return section here is where you can expose any internal functions of variables you want people to be able to access from the sandbox. With the revealing module pattern, the only thing visible from an outer context is NAME on the first line. If you wanted other scripts to be able to call the handleInput() function, you would need to put that inside the return (and you can optionally define a new reference for it), so return { accessHandleInput: handleInput() } would allow other scripts to invoke the handleInput() function by the reference NAME.accessHandleInput(). We won't need this for now. Fully understanding this pattern is pretty advanced - don't worry if it's not making a huge amount of sense. The important thing for now is the syntax on the first and last lines that wrap the boilerplate. Don't mess up any of those braces or parentheses, or it won't work. So, with that out of the way, a rough outline of your script might look like this... I did totally forget to ask which system you're using. But this is a 5e example of a recharge macro you might use, just with @{selected} calls for now. I've thrown the word 'rechargeBot' in between the template properties - this gives us a keyword to look for in the API script which isn't displayed in chat. It's preferable to just searching for the word 'recharge' since that could appear in other, unrelated chat: &{template:npcaction} rechargeBot {{rname=Recharge Item: @{selected|character_name}}} {{description=Item: **@{selected|other_resource_name}** Roll: [[1d6cf<5cs6]]}} And then an outline of the script: const rechargeBot = (() => { // eslint-disable-line no-unused-vars const version = '0.1.0' ; const getRechargeData = ( msg ) => { // This function grabs the player, character, item and roll details from the macro posted to chat; // Returns an object holding everything we need from the recharge macro // example output: // { // characterName: 'bob', // characterId: '-sd8vh13hWsdgnoisdg', // playerId: '-sdlngs89yhtg342o8gh', // itemName: 'wand of silly walks', // rollIndex: 0 // } // If anything goes wrong it returns null; } const checkRechargeRoll = ( inlineRolls , rollIndex , targetNumber = 6 ) => { // This function takes the inline rolls from the posted chat message, and the index we supply it to check if // the recharge was successfull // We've supplied a default parameter here with targetNumber=6. That means you don't need to supply it, but have the // option to override it if you wish // has three returns: true (recharge successful), false (recharge unsuccessful), null (an error occurred) } const findResourceCount = ( resourceName ) => { // This function takes the resource name as input (e.g. 'wand of fireballs') and locates the required attributes // You *could* hard-code this to @{other_resource} or similar, but then the script can never handle a // character with two wands. Much better to be able to search by resource name, then we can find multiple // resources in other_resource, class_resource, or the repeating_resource section below // returns an object containing the resource attributes required. // the fastest way to set an attribute is with it's unique id, so we'll return that with the data // example return of a resource located at other_resource: // // { // name: 'other_resource', // id: '-NADSFUHg8sdhq3oh98ghs', // current: 1, // max: 3 // } } const rechargeItem = ( resourceData ) => { // This function does some error checking, then recharges the item if valid // e.g. check if the current charges is a number, or if the item is already at max // instead of returning a value from this function, we'll send the result to chat // with the 'sendResponse()' function below. // That will be the end point of a full run of the script } const sendResponse = ( content , chatTarget ) => { // If no char target is supplied, post the message publicly const messageTarget = ( chatTarget ) ? `/w " ${ chatTarget } " ` : `` ; // this is a ternary statement - a shorthand if/else. sendChat ( 'rechargeBot' , ` ${ messageTarget } ${ content } ` ); // these ${myVar} are template literals, an easy way to insert variables into Strings // Template literals require using ` backticks ` around them. I highly recommend getting used to them, as you can freely use // other punctuation (like '' and "") inside the backticks without messing up the code. } const handleInput = ( msg ) => { log ( msg ); // When you're starting out, LOG EVERYTHING! Roll20 can do some unexpected things, and it'll take forever to // bug hunt if you don't have logs. For example, someone unfamiliar with Roll20 might assume our parameter // here 'msg', is a String. It isn't and trying to use a String method will cause a crash. // 'msg' has a bunch of useful keys on it, check the wiki 'api chat events' section for details if ( msg . type === "api" && msg . content . indexOf ( 'rechargeBot' ) > -1 ) { // Recharge macro output found! The script moves to the main functions... const rechargeData = getRechargeData ( msg ); log ( rechargeData ); if ( rechargeData == null ) { // Keep error checking as we move through the script to prevent crashes down the line. return ; // (myVar == null) is a handy shortcut for checking if something is null OR undefined } // Check if the roll was successful... const successfulRoll = checkRechargeRoll ( msg . inlineRolls , rechargeData . rollIndex ); log ( successfulRoll ); if ( successfulRoll == null ) sendResponse ( 'An error occurred' , msg . who ); //msg.who holds the name of the person who posted the message else if ( successfulRoll === false ) sendResponse ( 'Roll failed' , msg . who ); else if ( successfulRoll === true ) { // Hooray! The player got a good outcome and the script gets to do more stuff! const itemCharges = findResourceCount ( rechargeData . itemName ); log ( itemCharges ); if ( itemCharges == null ) { sendResponse ( `Couldn't find item in Resources: " ${ rechargeData . itemName } "` , msg . who ); return ; } else { rechargeItem ( itemCharges ); } } } // The script will silently end here if we don't find our trigger word, which is fine. // But you may want to put an 'else' branch in here anyway, just for peace of mind while you're learning else { // This will make it obvious that the script executed when you check the API log log ( `rechargeBot didn't find a recharge macro.` ); } }; on ( 'ready' , () => { on ( 'chat:message' , handleInput ); }); })(); There's a whole heap of comments in there, but hopefully that breaks down the task into some more understandable chunks. The functions are (mostly) written in reverse order - the entry point is down the bottom, the next function to run is handleInput(), which is second-to-bottom and so on. This isn't necessary in every instance due to the revealing module structure of the outer wrapper - you *can* put handleInput() above the other functions if you wish. But I'd recommend trying to stick to the normal order until you understand the pattern a bit better - and the general syntax is you need to declare a function earlier in the script than when you invoke it. One other thing about this example handleInput() - it's pretty heavily loaded. That's not a big concern for this script, as we're only watching for one specific input. However, if your script has a bunch of different inputs to react to, you would absolutely want to move almost all of that code out of handleInput, and into something like handleRechargeMacro(). This lets you keep your handleInput() function as a clean and readable index of the paths through your script: const handleInput = ( msg ) => { if ( msg . type === "api" ) { if ( msg . content . indexOf ( 'rechargeBot' ) > - 1 ) handleRecharge (); else if ( msg . content . indexOf ( 'blahblah' ) > - 1 ) handleBlahblah (); else if ( msg . content . indexOf ( 'spelllevel=' ) > - 1 ) handleSpellcast (); } }