scmJS docs

How an edit works

Reading is a plain call. Changing has two shapes, both the editor's.

Read, change, apply. The settings and the trigger list come out as a value and go back with an apply. The apply marks the sections it touched so the next write includes them; changing scenario.triggers by hand does not.

const triggers = readTriggers(scn);
triggers.push(newTrigger());              // owned by All Players unless told otherwise
applyTriggers(scn, triggers);

Change lists. Units and locations produce a list of changes, which applyUnitChanges or applyLocationChanges carries out — or undoes, with "undo". That is the editor's undo history, and it is what makes a script's edit cheap to reverse.

const serial = nextSerial(scn);
const changes = addUnits(scn, [makeUnit(null, START_LOCATION, 0, 10 * 32, 10 * 32, serial)]);
applyUnitChanges(scn, changes);

Positions are in pixels (a tile is 32). The null passed to makeUnit is the unit table from the game data; without it a unit gets no default resources or hit points, which is what a start location needs anyway.

By hand. A field of the scenario can also be set directly, as long as the section it is written to is marked with markDirty(scn, "SIDE"); an unmarked change is not saved. The unit, upgrade and technology tables are each written to one or both of a pair (UNIS and UNIx, …) depending on the map's revision and what the file has already, and unitSettingsSections(scn), upgradeSettingsSections, upgradeRestrictionSections, techSettingsSections and techRestrictionSections name the ones to mark.

scn.playerRaces[0] = PlayerRace.Protoss;
markDirty(scn, "SIDE");
// after a change to scn.unitSettings:
markDirty(scn, ...unitSettingsSections(scn));