Here's some code that should do what you need, with some name tweaks. I;ll post an explanation of each part, then the completed thing at the bottom. So first you need the sheet opened: on('sheet:opened', () => { // log statement that you are checking version getAttrs(['codeversion'], v => { versionator(parseInt(v.codeversion) || 0); }); //log statement that version check is complete }); This checks the attribute you have named for the version checking. It can be the same as your normal version attribute. This calls a separate function to do the actual version checking. This is important because sometimes your version checking will call it again, so it needs to be separated from the sheet: opened event. So, then the version checking function. There's more than one way to build this. It could look like this: const versionator = (codeversion) => { if(codeversion < 1) { version_0_to_1(); } else if(codeversion < 2) { version_1_to_2(); } else if(codeversion < 3) { version_2_to_3(); } }; or this: const versionator = (codeversion) => { switch(true) { case (codeversion <1): version_0_to_1(); break; case (codeversion <2): version_1_to_2(); break; case (codeversion <3): version_2_to_3(); break; default: // log statement that no change is needed? } }; Or the order could be reversed, going from highest version to low (and reversing the equality comparison). All these versions of the function do exactly the same thing. They accept a version parameter, and then go through steps to see if the version is less than a threshold, and if so run a version upgrade function. Think about what happens if you r sheets code is up to version 3, and someone has never opened their sheet since version 0. The versionator will run, find they are below version 1, and so run the code that upgrades it to version 1, and then stop. (I'll explain why it is important that it stops after one and only upgrade, below.) So in the function that upgrades to version 1, we have to run versionator again. So we would have a function that looks at base like this: const version_0_to_1 = () => { getAttrs(['some', 'attributes', 'to', 'change'] = (v) => { // create an object to hold the new attributes const update = {}; // transfer to old attributes into the new attributes update.new_some = v.some; update.new_attributes = v.attributes update.new_to = v.to; update.new_change = v.change; // add the version attribute const codeversion = 1; update.codeversion = version setAttrs(update, // send the new attributes to the sheet {silent:true}, // you probably want it not to trigger sheet workers versionator(codeversion)); // and run the versionator function again }); }; So some things to note here: you add the new version number to the updated attributes. and you include a new call to the versionator function inside the setAttrs function. This is really important. It means that it only runs after the sheet attributes have been updated. And you include the version number in the paremeters. So now, the sheet has been upgraded to version 1, and goes back to versionator. The first check - is it below 1? - is run again, and it passes that. It reaches the second check - is it below 2? Since it isnt, that function now runs. And when it ends, it send back to the versionator, and so on. So, this seems a little convoluted. It would be easier to run the versionator without stopping after each upgrade, just let it run and complete all 3 upgrades (in this example). But setAttrs is asynchronous - it starts to work but doesnt finish immediately. The rest of the code continues simultaneously, without waiting for the setAttrs process to finish. So you could easily have the version 3 upgrade running at the same time as the version 1 upgrade - and the version 3 upgrade might complete before the version 1 upgrade does. Sometimes, the order things happen can lead to different results. When doing sheet updates it's especially important that upgrades complete properly. With the code being constructed this way, you can update a sheet multiple times, and be confident that each update is fully complete before the next one starts. So, with that explanation out of the way, here's code that will serve your purpose: on('sheet:opened', () => { // log statement that you are checking version getAttrs(['codeversion'], v => { versionator(parseInt(v.codeversion) ||0); }); //log statement that version check is complete }); const versionator = (codeversion) => { if(codeversion < 1) { version_0_to_1(); } }; const version_0_to_1 = () => { // log statement that you are updating to version 1 getSectionIDs('repeating_spells', idarray => { const fieldnames = []; idarray.forEach(id => fieldnames.push(`repeating_spells_${id}_level`)); getAttrs(['NAMEWITHTYPO', ...fieldnames], v => { const update = {};
const codeversion = 1;
update.codeversion = codeversion; update['NAMEWITHOUTTYPO'] = v['NAMEWITHTYPO']; idarray.forEach(id => { update[`repeating_spells_${id}_spell_level`] = v[`repeating_spells_${id}_level`]; }); // log statement that you have completed updating to version 1 setAttrs(update,{silent:true}, versionator(codeversion)); }); }); } Change each instance of NAMEWITHTYPO and NAMEWITHOUTTYPO to what they should be. Likewise I've used repeating_spells for your section name, and spell_level for your new spell level name. Change them to what you need them to be.