Skip to main content

Affinity SDK — Quick Reference Cheat Sheet

Keep this file open while coding. One page covering the patterns you'll use every session.


Boilerplate — every script starts here​

'use strict';

const { Document } = require('/document.js');

const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
// your code here
}

Imports by task​

// Document access
const { Document, FileExportOptions, FileExportArea, DocumentPromises } = require('/document.js');
const { app } = require('/application.js');

// Making changes
const { DocumentCommand, CompoundCommandBuilder, AddChildNodesCommandBuilder } = require('/commands.js');

// Selections
const { Selection, TextSelection } = require('/selections.js');

// Colours and fills
const { RGBA8, Colour } = require('/colours.js');
const { FillDescriptor, SolidFill } = require('/fills.js');

// Strokes
const { LineStyle, LineStyleMask } = require('/linestyle.js');

// Geometry
const { Transform, CurveBuilder, PolyCurve } = require('/geometry.js');

// Text
const { StoryBuilder } = require('/storybuilder.js');
const { StoryDelta } = require('/storydelta.js');
const { GlyphAttStringType } = require('/glyphatts.js');

// Dialogs
const { Dialog, DialogResult, DialogSpatialAnchor, SpatialAnchor } = require('/dialog.js');

// Units
const { UnitType } = require('/units.js');

// Collections
const { Collection } = require('/collection.js');

// Node tree
const Nodes = require('/nodes.js');

// Files
const { File } = require('/fs.js');
const { Buffer } = require('/buffer.js');
const { HttpRequest, RequestMethod } = require('/network.js');

// System / logging / config
const { OS } = require('/os.js');
const { Environment } = require('/environment.js');
const { LogFile, LogLevel } = require('/logging.js');
const { ConfigurationItem } = require('/configuration.js');

Programmatic selection​

const { Selection } = require('/selections.js');
const { DocumentCommand } = require('/commands.js');

// Select a single node
const sel = Selection.create(doc, targetNode);
doc.executeCommand(DocumentCommand.createSetSelection(sel));

// Select multiple nodes
const multiSel = Selection.create(doc, node1, node2, node3);
doc.executeCommand(DocumentCommand.createSetSelection(multiSel));

The command loop​

const sel = doc.selection;
const compound = CompoundCommandBuilder.create();

for (const node of sel.nodes) {
const nodeSel = Selection.create(doc, node);
compound.addCommand( DocumentCommand.createXxx(nodeSel, ...) );
}

doc.executeCommand(compound.createCommand());

Colours​

const red = RGBA8(220, 50, 50, 255); // r, g, b, a — all 0-255
const clear = RGBA8(0, 0, 0, 0);
const fill = FillDescriptor.createSolid(red);
const noFill = FillDescriptor.createNone();

Apply fill / stroke / opacity​

// Via DocumentCommand — selection first, then fill
doc.executeCommand(DocumentCommand.createSetBrushFill(sel, fill)); // interior
doc.executeCommand(DocumentCommand.createSetPenFill(sel, fill)); // stroke

// Via doc convenience methods — accept Colour, SolidFill, or FillDescriptor
doc.setBrushFillDescriptor(colour, sel);
doc.setPenFillDescriptor(fill, sel);

// Opacity and blend mode
doc.setOpacity(0.5, sel); // layer opacity 0.0–1.0
doc.setBrushFillOpacity(0.7, sel); // fill opacity
doc.setLineFillOpacity(0.5, sel); // stroke opacity

// Line style shorthands
doc.setLineWeight(4, sel); // pixels
doc.setLineWeightPts(2, sel); // points
doc.setLineCap(cap, sel);
doc.setLineJoin(join, sel);

// Common doc convenience methods
doc.undo(); doc.redo();
doc.selectAll();
doc.deleteSelection(sel);
doc.showSelection(sel); doc.hideSelection(sel);
doc.lockSelection(sel); doc.unlockSelection(sel);
doc.convertToCurves(sel);
doc.rasteriseObjects(sel);
doc.flatten(); doc.mergeVisible();

Text replacement​

// end - 1 excludes the hidden story terminator
doc.executeCommand(DocumentCommand.createSetCurrentSpread(doc.currentSpread));

const si = node.storyInterface;
const range = si.storyRange;
const textSel = TextSelection.create([{ begin: range.begin, end: range.end - 1 }]);
const selection = Selection.create(doc, node); // NOT Selection.createEmpty()
selection.addSubSelectionForNode(node, textSel);
doc.executeCommand(DocumentCommand.createSetText(selection, 'New text'));

Dialog​

const dlg = Dialog.create('My Tool');
const col = dlg.addColumn();
const grp = col.addGroup('Options');

// Attach controls to dlg for clean access in helper functions
dlg.enable = grp.addCheckBox('Enable', false);
dlg.format = grp.addComboBox('Format', ['PNG', 'PDF'], 0);
dlg.colour = grp.addColourPicker('Colour');
dlg.size = grp.addUnitValueEditor('Size', UnitType.Pixel, UnitType.Millimetre, 100); // store px, display mm
dlg.count = grp.addUnitValueEditor('Count', UnitType.Number, UnitType.Number, 6, 1, 100); // unitless integer
dlg.anchor = grp.addSpatialAnchor('Anchor', SpatialAnchor.None); // spatial anchor picker

// Reactive — per-control (no argument)
dlg.enable.onValueChangedHandler = () => { dlg.size.isEnabled = dlg.enable.value; };

// Reactive — dialog-level (receives changed control)
dlg.onControlValueChangedHandler = (ctrl) => { /* ctrl === dlg.enable etc. */ };

// Simple run
const result = dlg.runModal();
if (result == DialogResult.Ok) { // note: Ok not OK (title-case)
console.log(dlg.enable.value, dlg.format.selectedIndex, dlg.colour.value);
}

// Live preview loop
dlg.onControlValueChangedHandler = () => doc.executeCommand(cmd, true); // true = preview
while (dlg.runModal() == DialogResult.Ok) {
if (!valid) { alert('Fix this.'); continue; }
doc.executeCommand(cmd); break;
}
doc.clearPreviews(); // ALWAYS — even on cancel

Export​

const desktop = app.userDesktopPath;
const opts = FileExportOptions.createWithPresetName('PNG');
const area = FileExportArea.createForWholeDocument();
const records = doc.export(`${desktop}/output.png`, opts, area, null);
console.log(records.all[0]?.isSuccess ? 'OK' : 'FAIL');

Layer effects (shadow example)​

// sel must be Selection.create(doc, node) — not doc.selection
// Effect commands must be executed individually — CompoundCommandBuilder is a silent no-op for effects
const idx = 0;

doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectColour(sel, RGBA8(0,0,0,180), idx, true));
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectOffsetAngle(sel, 6, 315, idx, true));
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, idx, 8, true));
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectOpacity(sel, 0.7, idx, true)); // opacity first, then index
// true = enableIfDisabled — adds the effect if not already present

Outline with gradient/pattern fill​

// Outline accepts a FillDescriptor (not just solid colour)
const { Gradient } = require('/colours.js');
const { GradientFill, FillDescriptor } = require('/fills.js');
const gradient = Gradient.create([
{ colour: RGBA8(255, 0, 0, 255), position: 0 },
{ colour: RGBA8(0, 0, 255, 255), position: 1 },
]);
const fill = FillDescriptor.create(GradientFill.create(gradient, 0), true);
doc.executeCommand(DocumentCommand.createSetOutlineLayerEffectFill(sel, fill, 0, true));

Async pipeline​

async function main() {
const docP = await DocumentPromises.getCurrent();
if (!docP) { console.log('No doc.'); return; }

try {
await docP.executeCommand(someCmd);
// mustSaveAs = true for new documents — use saveAs(path) instead of save()
if (docP.document.mustSaveAs) {
const { app } = require('/application.js');
await docP.saveAs(app.userDesktopPath + '/output.af');
} else {
await docP.save();
}
} catch (err) {
console.log('Error:', err);
}
}
main(); // always invoke the async entry point

Additional APIs​

Pixel read/write​

const { PixelReaderWriterRGBA8 } = require('/pixelaccessor.js');
const bitmap = node.rasterInterface.createCompatibleBitmap(true);
const rw = PixelReaderWriterRGBA8.create(bitmap);
const px = rw.readPixel(x, y); // { r, g, b, alpha } — NOT { r, g, b, a }
rw.writePixel(x, y, { r, g, b, alpha }); // key is 'alpha' on write too
// // using 'a:' silently writes 255 — no error!
doc.executeCommand(DocumentCommand.createReplaceBitmap(sel, bitmap));

Font enumeration​

const { Font, FontFamily } = require('/fonts.js');
const families = FontFamily.all; // all installed families
const fonts = Font.all; // all individual fonts

// Get a specific font via FontFamily
const arial = FontFamily.all.find(f => f.name === 'Arial');
const f = arial?.fontCount > 0 ? arial.getFont(0) : null;
if (f) console.log(f.isSerif, f.isMonospaced, f.postscriptName);

Custom node tags​

const { PredefinedTagKey } = require('/taginterface.js');
// Write a tag (persists in file)
DocumentCommand.createSetTagValueForKey(sel, 'my-key', 'value');
DocumentCommand.createSetTagValueForPredefinedKey(sel, PredefinedTagKey.ScriptLabel, 'label');
// Read tags back via tagInterface
if (node.tagInterface) {
console.log(node.tagInterface.hasKey('my-key'));
console.log(node.tagInterface.getValueForKey('my-key'));
}

Opacity commands​

DocumentCommand.createSetOpacity(sel, 0.5) // layer opacity
DocumentCommand.createSetBrushFillOpacity(sel, 0.8) // fill opacity
DocumentCommand.createSetLineFillOpacity(sel, 0.6) // stroke opacity

ContainerNode (named layer group)​

const { ContainerNodeDefinition } = require('/nodes.js');
const { AddChildNodesCommandBuilder } = require('/commands.js');
const def = ContainerNodeDefinition.create('My Group'); // named container
const builder = AddChildNodesCommandBuilder.create();
builder.addContainerNode(def);
doc.executeCommand(builder.createCommand(true));

doc.addNode(def); // shorthand for single-node add (no builder needed)

Node convenience methods​

node.userDescription = 'Renamed'; // set layer name directly
node.lock(); // make non-editable
node.unlock();
const copy = node.duplicate(); // returns new node (lands on top)
node.delete(); // remove node
if (node.isColouredLogicalNode) {
console.log('Colour label:', node.layerColour); // read-only
}

Application settings​

const { app } = require('/application.js');
app.settings.undoLimit // read-only
app.settings.loadPSDWithEditableText // read/write
app.settings.loadPSDWithEditableText = true

Document save and close​

const { PackageResourcesPolicy } = require('/document.js');
doc.save(); // save in-place
doc.saveAs('/path/to/file.af');
doc.saveAsPackage('/path/pkg', PackageResourcesPolicy.CopyLinks);
// doc.close(); // close (no save prompt — save first; handle invalid after)

Macro commands​

DocumentCommand.createStartRecordingMacro()
DocumentCommand.createStopRecordingMacro()
DocumentCommand.createReplayMacro()
DocumentCommand.createImportMacro(desktop + '/macro.afmacro')

Raster selection commands​

DocumentCommand.createRasterSelectAll()
DocumentCommand.createRasterDeselect()
DocumentCommand.createRasterInvertSelection()

// Renamed from rasterSelectPolygon
DocumentCommand.createSetRasterSelectionFromPolygon(polygon, operation, antialias, feather)

// Selection from object bounds
DocumentCommand.createSetRasterSelectionFromObject(sel, operation, antialias, feather)

// Refine existing raster selection
DocumentCommand.createGrowShrinkRasterSelection(10, false) // grow by 10px (circular kernel)
DocumentCommand.createFeatherRasterSelection(5) // soften edge by 5px
DocumentCommand.createSmoothRasterSelection(3) // smooth boundary by 3px

const { RasterSelectionOutlineAlignment } = require('/commands.js');
DocumentCommand.createOutlineRasterSelection(4,
RasterSelectionOutlineAlignment.Centre, false) // outline at radius 4px

// Doc convenience methods — same signatures without create*
doc.growShrinkRasterSelection(8, true); // grow, circular kernel
doc.featherRasterSelection(5);
doc.smoothRasterSelection(3);
doc.outlineRasterSelection(4,
RasterSelectionOutlineAlignment.Inside, false);

Drawing scale​

const { DrawingScale } = require('/drawingscale.js');
const scale = DrawingScale.createFromIntegers(1, 100, true); // 1:100

createSetEditable — argument order​

// Selection is first argument, editable flag is second
DocumentCommand.createSetEditable(selection, false) // lock
DocumentCommand.createSetEditable(selection, true) // unlock

Document convenience shorthands​

// Text operations
doc.setText('Hello', sel); // replace text in selection
doc.formatText(delta, sel); // apply formatting to selection
doc.insertGlyph(0x2665, sel); // insert ♥ glyph

// Undo/redo
doc.undo();
doc.redo();

// Stroke enhancements
doc.setDashPhase(0, sel); // dash phase
doc.setBalancedDashes(true, sel); // balance dashes at corners

// Dash pattern variants
doc.setDashPattern([8, 4], sel); // regular dash
doc.setDashPattern([8, 4, 2, 4], sel); // dash-dot-dot

Document line style shorthands​

// These doc.setXxx() shorthands are simpler than building commands manually
doc.setLineWeightPts(2, sel); // set weight in points (auto-converts to px)
doc.setLineCap(2, sel); // 0=Butt, 1=Square, 2=Round
doc.setLineJoin(2, sel); // 0/1=Miter, 2=Round, 3=Bevel
doc.setLineType(LineType.Dash, sel); // set line type
doc.setDashPattern([8, 4], sel); // set dash pattern
doc.setStrokeAlignment(StrokeAlignment.Centre, sel); // stroke alignment

StoryDelta convenience factories​

const { StoryDelta } = require('/storydelta.js');
const { FontWeight } = require('/fonts.js');

// Simple bold + italic
const bold = StoryDelta.createWeight(FontWeight.Bold);
const italic = StoryDelta.createItalic(true);
const combined = StoryDelta.createComposite([bold, italic]);
doc.formatText(combined); // applies to current selection — no Selection object needed

Geometry utilities​

const { unionRects, TransformBuilder, Polygon, Curve, CubicBezier, Point } = require('/geometry.js');
const combined = unionRects(rect1, rect2); // merge two bounding boxes

// Polygon convenience factories
const tri = Polygon.createTriangle(Point.zero, new Point(100, 0), new Point(50, 80));
const dia = Polygon.createDiamond(new Point(0, 0), new Point(100, 100));
const loz = Polygon.createLozenge(new Point(0, 50), new Point(100, 50), 20);

// Precision unit circle (for measuring/fitting)
const circle = Curve.createPrecisionUnitCircle();
// Transform properties
const tx = node.baseToSpreadTransform;
console.log(tx.xAxis, tx.yAxis, tx.origin);
const inv = tx.inverted;

// Chainable transform methods
const moved = Transform.translationMatrix(100, 0); // translate
const scaled = Transform.scaleMatrix(2, 2, point); // scale about point
const added = moved.translate(50, 0).scale(1.5, 1.5, origin); // chainable

// around() replaces about()
const rot = Transform.rotationMatrix(45, Transform.Degrees); // 45deg about origin
const rot2 = Transform.rotationMatrix(45, Transform.Degrees, center); // about center

// Static multiply, decompose
const combined2 = Transform.multiply(txA, txB); // combine transforms
const [sc, tr, rot3] = Transform.decompose(combined2); // decompose

// TransformBuilder fluent builder
const builder = TransformBuilder.create();
const built = builder
.setXAxis(1, 0)
.setYAxis(0, 1)
.setOrigin(100, 100)
.build();

// CubicBezier prototype methods
const bez = new CubicBezier();
const pt = bez.evaluate(0.5); // point at t=0.5
const { left, right } = bez.split(0.5); // split into two beziers
const tangent = bez.getTangent(0.5); // tangent vector
const normal = bez.getNormal(0.5); // normal vector
const curv = bez.getCurvature(0.5); // curvature value
const closest = bez.getClosestPoint(pt); // nearest point on curve
const len = bez.length; // total arc length

HalftoneFilter​

const { HalftoneFilter } = require('/layereffects.js');

// Direct apply (no CompoundCommandBuilder — silent no-op for layer effects)
doc.executeCommand(DocumentCommand.createSetHalftoneParameters(
sel, HalftoneFilter.Screen, HalftoneFilter.Sine, 0.5, 45, 0.1
));

// Or use live HalftoneFilterRasterNode
const { HalftoneFilterRasterNodeDefinition, HalftoneScreenType, HalftoneDotType } = require('/nodes.js');
const def = HalftoneFilterRasterNodeDefinition.createDefault();

DialogButton and DialogColumnStack​

// Button — use addButton() on a group
const btn = grp.addButton('Click Me');
btn.alignment = HorizontalAlignment.Left;
btn.onClickHandler = () => { console.log('Clicked!'); };

// Column stack layout
const stack = grp.addColumnStack();
const leftCol = stack.addColumn();
const rightCol = stack.addColumn();

// Detect if dialog is running modal (property on Dialog instance)
if (dlg.isRunningModal) { console.log('Dialog is modal.'); }

// Toggle item visibility (method on Dialog instance)
dlg.setItemsVisibility([label.itemID], []); // show label
dlg.setItemsVisibility([], [label.itemID]); // hide label

// Fill/stroke editor
const { DialogFillEditor, DialogStrokeEditor } = require('/dialog.js');
const fillEditor = DialogFillEditor.create();
const strokeEditor = DialogStrokeEditor.create();

// Font picker
dlg.font = grp.addFontPicker('Font'); // DialogFontPicker — .font, .fontFamily, .fontCollection

// Find a control by ID (returns typed DialogControl or null)
const ctrl = dlg.findControl('my-control-id');
if (ctrl) ctrl.isEnabled = false;

Fills — selection first​

const desc = FillDescriptor.create(SolidFill, { r: 255, g: 0, b: 0, alpha: 255 });
DocumentCommand.createSetBrushFill(sel, desc); // selection first, then fill
DocumentCommand.createSetPenFill(sel, desc);

// Hatch fill
const hatchCmd = DocumentCommand.createSetHatchFillAttributes(sel, /* builder */);

TableTextNode​

const { TableTextNodeDefinition } = require('/nodes.js');
const def = TableTextNodeDefinition.create({ x: 0, y: 0, width: 300, height: 200 }, { width: 3, height: 3 });
doc.addNode(def);

TableSubSelection — read table cell/edge state​

const { TableSubSelection } = require('/selections.js');
// After selecting a TableTextNode, check for table sub-selection
const item = doc.selection.items[0];
const sub = item.getSubSelectionOfType(SubSelectionType.Table);
if (sub) {
const ts = TableSubSelection.fromSubSelection(sub);
console.log(ts.anchor, ts.caret, ts.isRectangle);
ts.enumerateCells((row, col, rect) => { /* ... */ });
}

NodeChildren.all — recursive descendants​

// node.children.all returns ALL descendants (not just direct children)
for (const child of node.children.all) {
if (child.isRasterNode) console.log('Found raster:', child.userDescription);
}

GlyphAtts / ParagraphAtts​

const { GlyphAtts, GlyphAttStringType, GlyphAttDoubleType } = require('/glyphatts.js');
const { ParagraphAtts } = require('/paragraphatts.js');

const ga = GlyphAtts.create();
ga.setStringValue(GlyphAttStringType.StyleName, 'Heading');
ga.setDoubleValue(GlyphAttDoubleType.Height, 24);

const pa = ParagraphAtts.create();
pa.isPreventWidows = true;

Attribute runs on Story​

for (const run of story.glyphAttRuns) {
console.log(run.begin, run.end, run.glyphAtts);
}
// Also: story.paragraphAttRuns — yields {paragraphAtts, begin, end}
// Also: story.attRuns — yields {glyphAtts, paragraphAtts, begin, end}

Fluent node-definition builders​

const def = PolyCurveNodeDefinition.createDefault()
.setUserDescription('My Shape')
.setTransform(xf);

Colour → FillDescriptor auto-convert​

// Fill/line methods accept Colour directly
def.addBrushFillDescriptor(RGBA8(200, 80, 40, 255));

node.spreadBaseBox​

const box = node.spreadBaseBox; // equivalent to getSpreadBaseBox(true)

Spread / artboard document properties​

const { ArtboardDocumentProperties } = require('/documentproperties.js');
const props = ArtboardDocumentProperties.create().setUseMargin(true).setMargin(margin);
doc.setArtboardDocumentProperties(artboard, props);
doc.setArtboardSizeWithAnchor(artboard, 400, 300, SpatialAnchor.Centre);

Boolean operations & shape division​

// Combine/divide overlapping vector nodes (needs 2+ selected)
doc.boolOpUnion(); // or DocumentCommand.createBoolOpUnion(doc.selection)
doc.boolOpSubtract(); // top shape cuts the shape(s) below
doc.boolOpIntersect(); // keep only the overlap
doc.boolOpXor(); // keep everything except the overlap
doc.divideShapes(); // split into pieces along intersections

Text frame threading​

// Thread text from one frame into another (no doc convenience method)
doc.executeCommand(DocumentCommand.createLinkTextFrame(srcFrame, destFrame));
doc.executeCommand(DocumentCommand.createUnlinkTextFrame(srcFrame));
// srcFrame.textFrameInterface.isMultiFrameTextFlow → true when threaded

Raster auto-fixes & fill​

doc.rasterAutoLevels(); // one-click tonal correction on selection
doc.rasterAutoContrast();
doc.rasterAutoColours();
doc.rasterAutoWhiteBalance();
doc.rasterFill(); // fill raster selection with current colour

System / logging / config​

OS.platform; // "Darwin"
Environment.permissions; // { fileSystem, network, genAI }
Environment.fileSystemRoots; // ["/Users/you/Desktop"]

const log = LogFile.create(`${app.userDesktopPath}/run.log`, LogLevel.Info, true);
log.start(); log.flush(); log.stop();

const cfg = ConfigurationItem.createFromJsonString('{"width": 1200}');
cfg.value.width; // 1200

Per-node export config, glyphs, units & hatch​

// Per-node export settings — config must already exist (set once via UI Export persona)
const { ExportScale, ExportSize } = require('/exportconfig.js');
const cfg = node.exportableInterface.exportConfig;
cfg.formats[0].appendSize(ExportSize.createWithExportScale(ExportScale.createWithMultiplier(2)));
doc.executeCommand(DocumentCommand.createSetExportConfig(sel, cfg));

// Glyphs — typed character model (code point, not a string)
const { CharGlyph } = require('/glyphs.js');
const g = CharGlyph.create(0x2665);
// story.glyphs / story.charGlyphs / story.hardBreakGlyphs — pre-filtered collections

// DPI-based unit conversion
const pxPerPt = doc.unitValueConverter.getConversionFactor(UnitType.Point, UnitType.Pixel);

// Hatch pattern construction
const { HatchPattern, HatchLine } = require('/hatch.js');
const pat = HatchPattern.createEmpty();
pat.appendHatchLine(HatchLine.create({ x: 0, y: 0 }, Math.PI / 4, 8, [4, 2]));

Critical rules​

RuleWhy
require('/module.js') not ./moduleSDK module registry, not file system
doc.title not doc.name.name is undefined
RGBA8() not Colour.fromRGBA().fromRGBA doesn't exist
doc.executeCommand(cmd, true) for preview, doc.clearPreviews() after loopWithout clearPreviews(), cancelled previews stay on canvas permanently
Previews don't stack — rebuild in one command each updateSDK replaces previous preview; layering commands doesn't accumulate
result == DialogResult.OkUse title-case Ok not OK; loose equality == works correctly
range.end - 1 for text selectionsExcludes the hidden story terminator
Selection.create(doc, node) for textSelection.createEmpty() causes silent failure
Read doc.selection BEFORE setCurrentSpreadSpread change clears selection
Compound commands for multi-step changesOne undo step, not N separate entries
Desktop + granted folders for file accessOther paths → PERMISSION_DENIED
No toLocaleString()Not supported — use plain number or String(n)
Check String(ri.format) === 'RGBA8' before PixelReaderWriterRGBA8.create()Format mismatch throws INVALID_ARGS — format is an enum object, use String() to compare
bitmap.format returns a plain integer not a nameUse RasterFormat.keys/values to look up — see lesson 15 loading example
res.response.statusCode == 200 not res.statusCodestatusCode is on res.response, not res directly; body is res.response.content
create*Filter not createSet*FilterParameters to apply a new filtercreateSet* is a silent no-op if no filter exists yet
No CompoundCommandBuilder for effect commandsSilent no-op — execute each effect command individually
setTimeout(delay, callback) not setTimeout(callback, delay)SDK order is opposite to Node/browser
main() call at bottom of async scriptsAsync functions don't auto-run
Transform.around() — use around(), not about()about() does not exist
createSetRasterSelectionFromPolygon() for polygon selectionsPrefer this over rasterSelectPolygon()
require('/vectorbrush') fails — that module does not existUse /pathbrush for vector brushes, /rasterbrush for pixel brushes
ExportConfig has no constructorObtain via node.exportableInterface.exportConfig — set once via the UI Export persona
createSetEditable(sel, bool) — selection firstSelection is the first argument
createSetBrushFill(sel, fill) — selection firstSelection is the first argument, then the fill
dialog.setIsEnabledBy*() / setIsDisabledBy*() are deprecated no-opsDrive dependent state from value-change handlers; use setItemsVisibility() instead
TableTextNodeDefinition.create(rect, {width, height}) not create(rect, width, height)Grid dimensions are an object
formatText across cell separators only partially appliesFormat each cell individually in a loop
Dialog values are read-only after build phaseMust set values before runModal()
setCurrentSpread clears selectionRead doc.selection first
node.spreadBaseBox equivalent to getSpreadBaseBox(true)Convenience property, defaults includeClips to true
RasterSelectionOutlineAlignment — Centre, Inside, OutsideRequired for createOutlineRasterSelection
Fill/line methods on definitions accept Colour directlyA solid fill is created automatically
Node-definition setters are chainable.setUserDescription(), .setTransform(), etc. return this
DialogControl.setIsEnabled() works correctlyUpdates isEnabled properly
Handlers fire on user interaction onlyProgrammatic value changes do not trigger onValueChangedHandler
Geometry structs have proper wrapper typesRectangle, Point, Vector, etc. are documented types, not anonymous objects