To be clear, this sheet worker actually works, for once. :) I haven't tested it extensively yet, but early tests are positive. Its a stripped out version of the sheetworker from the old L5R sheet. That's why all the structure is there for converting data from repeating sections, even though there's no repeating data to convert this time. Basically, I've got this as a structure going forward. (I'll clean up before I publish). My intention is to copy and paste it every time Fantasy Flight changes a major element of the game in beta and I need to rename a non-derived variable because of it. So this is the first one, and they renamed "Resilience" to "Endurance." That stat is derived for PCs, so no worries there, just an orphaned variable on the sheet, but for NPCs, that stat is hand-picked and not derived from stats, so it needs to be converted by the sheetworker, which this seems to do without issue. Actually, a lot of this is a bit beyond me in syntax, I believe Brian and Jakob did most of the work, but I think Jakob the part I'm most curious about, and that's the section that checks, sees that there's no version number variable, and runs the sheetworker function. I need to understand how that works. Can you explain that well enough (or how to change it) so that when I have to write the next one, to check for version 0.8.1 and change it version X, I know how that bit of code changes? This I think checks to see if the variable is missing and runs the code. If I'm just moving variables around, I can probably do the rest, but I'm not sure how that If statement works well enough to alter it, because I don't understand the syntax well enough to know what parts to change. var convertFromOldSheet = function () { // Non-repeating attributes to rename let conversionData = { 'npc_resilience': 'npc_endurance' }, // Repeating attributes to rename; this assumes that the section name, e.g. // repeating_spell, does NOT change, so make sure that that is the case or // it will need quite a bit mor work conversionDataRepeating = { }; // Non-repeating attribute renaming getAttrs(Object.keys(conversionData), function (values) { let setting = {}; Object.keys(conversionData).forEach(function (oldAttr) { if (values[oldAttr] != null) { // set value of new attribute to the old one setting[conversionData[oldAttr]] = values[oldAttr]; // blank old attribute setting[oldAttr] = ''; } }); setAttrs(setting, {silent: true}); // set things silently to make sure we do not trigger unwanted changes }); // Repeating attribute renaming - loop through all sections, and loop through all ids // and all attributes for every section Object.keys(conversionDataRepeating).forEach(function (sectionName) { let data = conversionDataRepeating[sectionName]; getSectionIDs(sectionName, function (idArray) { let oldAttrs = idArray.reduce(function (m, id) { return m.concat(Object.keys(data).map(name => `${sectionName}_${id}_${name}`)); }, []); getAttrs(oldAttrs, function (values) { let setting = {}; idArray.forEach(function (id) { Object.keys(data).forEach(function (oldAttr) { if (values[`${sectionName}_${id}_${oldAttr}`] != null) { // set value of new attribute to the old one setting[`${sectionName}_${id}_${data[oldAttr]}`] = values[`${sectionName}_${id}_${oldAttr}`]; // blank old attribute setting[`${sectionName}_${id}_${oldAttr}`] = ''; } }); }); setAttrs(setting, {silent: true}); // set things silently to make sure we do not trigger unwanted changes }); }); }); }; on('sheet:opened', function () { let currentVersion = '0.8.1'; getAttrs(['version'], function (v) { if (!v.version) { convertFromOldSheet(); }; setAttrs({ version: currentVersion }); }); });