A user script runs against the open document, from Tools ▸ User scripts. It is ordinary JavaScript or TypeScript, and everything it can reach is listed on this page — this is the whole surface, generated from the same declaration that installs it.
Scripts run in an interpreter written in JavaScript, so a script has no network, no page, and no filesystem: not because they were taken away, but because nothing exists inside that sandbox except what appears below. The only effect a script can have is on your map.
1 — MIT licensed, so you can develop against it in your own editor.// Line up every crate on the Props layer to a whole tile.
const props = map.layer("Props");
if (!props) throw new Error("no Props layer");
let moved = 0;
for (const placement of props.placements) {
if (placement.type?.name !== "Crate") continue;
const x = Math.round(placement.x);
const y = Math.round(placement.y);
if (x === placement.x && y === placement.y) continue;
placement.move(x, y);
moved++;
}
console.log(`snapped ${moved} crates`);
// The whole run is one undo step: one Ctrl-Z puts all of it back. If this
// throws part-way, nothing it already changed is kept.Already defined when your script starts. Everything else is reached by drilling into these.
mapMapThe open document.selectionSelectionShorthand for map.selection.paramsobjectThis script's configuration, as filled in on the run dialog.eventsEventsListen for what the user does. Only useful to a script set to "stays running".Each is a live view of the document, not a copy: read a property twice and the second read sees any change in between.
Map8 membersThe open document. The root of everything a script can reach.
Layer14 membersOne layer. A tile layer also has tile methods; ask `kind` first.
Placement19 membersOne placed entity. A live view: read a property and you get the document's current value, not a snapshot from when you found it.
Tile7 membersOne painted cell.
EntityType3 membersA type from the project. Read-only — a script edits the map, not the project that defines what can go on it.
Selection3 membersWhat the user had selected when the script started.
Events4 membersWhat the user is doing, as it happens. Registering a listener keeps a script alive after its code ends — so only a script set to "stays running" can usefully use this, and it runs until you stop it.
A script set to stays running can listen for what you do and react to it. Registering a listener is what keeps it alive — a script whose code has ended and which is listening for nothing is finished, whatever its mode says.
// Keep every new crate on whole tiles, as they are placed.
events.on("placementsAdded", (event) => {
for (const placement of event.placements) {
if (placement.type?.name !== "Crate") continue;
placement.move(Math.round(placement.x), Math.round(placement.y));
}
});
// Or wait for one thing and carry on:
const saved = await events.once("documentSaved");
console.log(saved.ok ? "saved" : "save failed");{ includeRemote: true } if that is what you want.placementsChanged with forty placements in it, not forty events.selectionChangedThe selection changed. Fires for an empty selection too, so a script can react to a deselection.placementsPlacement[]Everything now selected.tilesTile[]Tile cells now selected.placementsAddedOne or more placements were added to the document.placementsPlacement[]The new placements.placementsChangedExisting placements moved, were resized, rotated, or had a property set.placementsPlacement[]The placements that changed.fieldsstring[]Which fields changed across the batch — "x", "y", "width", "height", "angle", "properties", "points".placementsRemovedPlacements were deleted. Ids rather than objects, because there is nothing left to hand you.idsstring[]Ids of what was deleted.tilesPaintedCells on a tile layer were painted or erased.layerLayerThe layer that changed.cells{ x: number; y: number }[]The cells that changed, painted and erased alike.layerChangedA layer was renamed, hidden, shown, locked, or unlocked.layerLayerThe layer.documentSavedA save finished. A script cannot save — this is how it hears that one happened.okbooleanFalse if the save failed.Exceeding any of these stops the script, names which one it was, and undoes whatever it was part-way through. Nothing it had already finished is kept either: a run is one undoable step, and so is one event.
You do not have to take a script's word for what it does. Set the speed to Crawl before pressing Run and it advances a few steps a frame, with the line it is on highlighted and the log filling as it goes; Pause, Step and Stop are beside it. Slowing a script down does not give it a bigger allowance — the clock only counts time it actually spends running.
One run5,000,000 steps · 5s · 20,000 changes · 200 log linesEach event500,000 steps · 250ms · 500 changes · 50 log linesEvent rate60 events per secondWorth knowing before you write one, because none of these fail in an obvious way if you assume otherwise.
fetch, document or require. A script cannot load anything or send anything anywhere.setTimeout and setInterval are removed: a script that scheduled work could never be said to have finished.Objects are views, so one can outlive the thing it points at — you keep a Placement, and it is deleted (by you, or by someone else in the session). Reading .exists is always safe and returns false; reading anything else throws an error naming the id. That is deliberate — silently returning nothing would let a script carry on editing what is no longer there.