Since you've got Pro, you can use this to fix all spells in the Campaign if you'd like. Run it with !savedcfix on ( 'ready' , () => { on ( 'chat:message' , ( msg ) => { if ( /api/ i . test ( msg . type ) && / ^ !savedcfix/ i . test ( msg . content ) && playerIsGM ( msg . playerid )) { let counter = 0 ; let npcList = findObjs ({ type : 'attribute' , name : 'npc' }) . filter ( a => a . get ( 'current' ) == 1 ) . map ( a => a . get ( 'characterid' )); let spellList = findObjs ({ type : 'attribute' }) . filter ( a => / ^ repeating_spell. * rollcontent/ . test ( a . get ( 'name' )) && npcList . includes ( a . get ( 'characterid' ))); let spellsNeedFixing = spellList . filter ( a => /template:spell/ i . test ( a . get ( 'current' )) && ! /{{savedc=/ i . test ( a . get ( 'current' ))); spellsNeedFixing . forEach ( a => { let currentMacro = a . get ( 'current' ), newMacro = currentMacro . replace ( '{{innate=@{innate}}}' , '{{innate=@{innate}}} {{savedc=@{spell_save_dc}}}' ); a . set ({ current : newMacro }); counter ++; }); let chatSummary = ` ${ npcList . length } NPCs found \n ${ spellList . length } NPC spells found \n ${ spellsNeedFixing . length } spells required fixing \n ${ counter } spells modified.` ; sendChat ( 'saveDCfix' , `/w gm &{template:default} {{name=saveDCfix Summary}} {{= ${ chatSummary } }}` ); } }); }); That'll find any NPC spells missing the {{savedc}} property and add it in. It uses the same string replacer as the character sheet, so hopefully it should catch all spells. If you're likely to be adding more spells as you go, you can also monitor for changes and auto-apply the fix. Although this has a bunch of very fast conditionals at the start to avoid running any laggy code unless necessary, it's still an extra handler running in the background for each added (not modified) Attribute. I'd say any increased latency would be absolutely unnoticeable since adding new spells is reasonably rare during actual game time, but still, only run it if you can't be bothered keeping up with the manual approach: on ( 'ready' , () => { on ( 'add:attribute' , ( ev ) => { if ( / ^ repeating_spell/ i . test ( ev . get ( 'name' ))) { if ( /rollcontent/ i . test ( ev . get ( 'name' ))) { let charId = ev . get ( 'characterid' ), npc = findObjs ({ type : 'attribute' , characterid : charId , name : 'npc' }), isNpc = npc . length ? npc [ 0 ]. get ( 'current' ) == 1 ? true : false : false ; if ( isNpc ) { let actualAttr = getObj ( 'attribute' , ev . id ); setTimeout (() => { let retries = 4 ; let awaitWorker = setInterval (() => { if ( actualAttr && actualAttr . get ( 'current' ) != '' ) clearInterval ( awaitWorker ); else { retries --; if ( retries < 1 ) return ; } }, 1000 ); if (! /template:spell/ i . test ( actualAttr . get ( 'current' ))) return ; if ( /{{savedc=/ i . test ( actualAttr . get ( 'current' ))) return ; let newMacro = actualAttr . get ( 'current' ). replace ( '{{innate=@{innate}}}' , '{{innate=@{innate}}} {{savedc=@{spell_save_dc}}}' ); actualAttr . set ({ current : newMacro }); let nameRef = actualAttr . get ( 'name' ). replace ( /rollcontent/ i , 'spellname' ); let char = getObj ( 'character' , actualAttr . get ( 'characterid' )), charName = char ? char . get ( 'name' ) : '' ; sendChat ( 'saveDCbot' , `/w gm &{template:default} {{name=saveDCbot}} {{NPC modded= ${ charName } }} {{Spell added=@{ ${ charName } | ${ nameRef } }}} {{saveDC fix=Applied}}` ); }, 500 ); } } } }); }); By the way, this will fail if it can't find the updated roll macro within 4.5 seconds. The Compendium knows nothing of the 5e sheet macro details and just drops the character-agnostic data, the sheetworkers then assemble to roll macro. If this takes an excessive amount of time to happen, or hangs, the spell won't be modified.