Skip to main content

Lesson 12 — Layer Effects & Adjustments

Layer effects — shadows, glows, outlines, bevels, blurs — are among the most visually impactful properties you can set from a script. This lesson covers the full effect system: applying and configuring individual effects, batching them across many nodes, applying adjustment layers, and using destructive filters on raster content.

Scripting effects is valuable wherever you need consistent visual treatment across layers: matching shadows on every icon in a set, a bloom on highlight layers, or clearing effects before re-exporting.

The effect system has three categories, each applied differently:

  • Live effects (shadows, glows, outlines, bevels) — applied via individual DocumentCommand calls; they persist as non-destructive properties on the node.
  • Adjustment layers (Levels, Curves, HSL, Brightness/Contrast) — created via *AdjustmentRasterNodeDefinition factories and added as live child nodes (see Lesson 16).
  • Destructive filters (Blur, Noise, Vignette, Bloom) — applied directly to raster pixel data; they can only be removed with undo.

One pattern matters for live effects: the enableIfDisabled flag. Per-property effect commands (e.g. createSetOuterShadowLayerEffectColour) modify individual properties of an existing indexed effect — they do not replace the whole effects list. When true, enableIfDisabled adds the effect at that index if it doesn't exist; when false, it silently does nothing if the effect isn't already present. For reliable updates without duplicates, the safer pattern is: read existing effects from node.layerEffectsInterface.effects, clone the effect at the target index, mutate its properties, then apply back via the full effect setter command.

By the end you'll be able to apply live effects to any node, set adjustment-layer parameters, apply destructive filters, and clear or replace effects in bulk.

What this lesson covers​

  • Outer shadow, outer glow, inner shadow, inner glow
  • Colour overlay, gradient overlay, bevel and emboss, phong bevel
  • Outline (stroke effect) and Gaussian blur layer effect
  • Adjustment layer parameters (set on existing adjustment nodes)
  • Destructive filters on raster nodes (create*Filter factory methods)
  • The enableIfDisabled flag and index parameter
  • Removing all effects at once
  • Drawing scale and margins
  • Raster brush properties

Effect categories​

The SDK splits effects into three categories:

CategoryExamplesHow they're applied
Layer effectsOuter/Inner Shadow, Outer/Inner Glow, Outline, Bevel & Emboss, Colour Overlay, Gaussian BlurIndividual doc.executeCommand calls per property using Selection.create(doc, node)
Adjustment layersLevels, Curves, HSL, VibranceCreated via *AdjustmentRasterNodeDefinition factories and/or modified via parameter commands
Destructive filtersBlur, Noise, Vignette, BloomRaster nodes only — use create*Filter factory methods

Outer shadow​

Apply shadow effects using OuterShadowLayerEffect.create() to build a complete effect object, then apply it with createSetOuterShadowLayerEffect. This is more reliable than individual property commands which have inconsistent argument orders.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');
const { OuterShadowLayerEffect } = require('/layereffects.js');

const doc = Document.current;
const node = doc.selection.nodes.first;

if (!node) { console.log('Select something first.'); }
else {
const sel = Selection.create(doc, node);
const effect = OuterShadowLayerEffect.create();

effect.colour = RGBA8(0, 0, 0, 180);
effect.offset = 6; // pixels
effect.angle = 315 * Math.PI / 180; // 315 degrees = bottom-right (radians)
effect.radius = 8; // blur radius in pixels
effect.opacity = 0.7; // 0.0–1.0
effect.enabled = true;

doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffect(sel, effect, 0));

console.log('Shadow applied.');
}

Note: createSetOuterShadowLayerEffectOffsetAngle and the .angle property use radians, not degrees. Convert with degrees * Math.PI / 180. OuterShadowLayerEffect.create() returns enabled = false and opacity = 0 by default — set these explicitly or the shadow will be invisible.


The enableIfDisabled parameter​

Most per-property effect commands accept enableIfDisabled as a final parameter. When true, the effect is automatically added at that index if it doesn't already exist. When false, the command silently does nothing if the effect isn't present.

// Snippet — for per-property commands only (not createSetOuterShadowLayerEffect)
// If there's no outer shadow, this creates one AND sets its radius
DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, 0, 10, true);

// If there's no outer shadow, this does nothing
DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, 0, 10, false);

The full createSetOuterShadowLayerEffect(sel, effect, index) setter shown in the example above does not use enableIfDisabled — it creates or replaces the effect at the given index directly. Prefer this approach over per-property commands for reliability.


The index parameter​

Effects that can be duplicated (OuterShadow, InnerShadow, Outline, ColourOverlay, GradientOverlay) use an index to address each instance. The first effect is index 0.

// Snippet — per-property commands with correct argument orders
// Radius: (sel, index, value, enable) — index before value
DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, 0, 8, true);

// Opacity: (sel, value, index, enable) — value before index (inconsistent!)
DocumentCommand.createSetOuterShadowLayerEffectOpacity(sel, 0.7, 0, true);

// Duplicate the first shadow, then address index 1
compound.addCommand(DocumentCommand.createDuplicateOuterShadowLayerEffect(sel, 0));
compound.addCommand(DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, 1, 20, false));

The per-property commands have inconsistent argument orders: radius uses (index, value) while opacity uses (value, index). The full effect setter avoids this problem entirely.


Outline (stroke effect)​

Outline adds a stroke as a layer effect (separate from the line style stroke on vector nodes). By default the outline uses a solid colour, but you can also use a FillDescriptor (gradient, bitmap fill, etc.) via createSetOutlineLayerEffectFill for gradient outlines.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { RGBA8 } = require('/colours.js');
const { Selection } = require('/selections.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (!node) { console.log('Select something first.'); }
else {
const sel = Selection.create(doc, node);

doc.executeCommand(DocumentCommand.createSetOutlineLayerEffectColour(sel, RGBA8(255, 0, 0, 255), 0, true)); // bright red
doc.executeCommand(DocumentCommand.createSetOutlineLayerEffectRadius(sel, 0, 20, true)); // 20px outline
}

For a gradient outline, use createSetOutlineLayerEffectFill with a FillDescriptor instead of a solid colour:

// Snippet — assumes sel, doc are defined
const { Gradient } = require('/colours.js');
const { GradientFill, FillDescriptor } = require('/fills.js');
const { RGBA8 } = require('/colours.js');

const gradient = Gradient.create([
{ colour: RGBA8(255, 0, 0, 255), position: 0 },
{ colour: RGBA8(0, 0, 255, 255), position: 1 },
]);
const gradFill = GradientFill.create(gradient, 0); // 0 = linear
const fill = FillDescriptor.create(gradFill, true);

doc.executeCommand(DocumentCommand.createSetOutlineLayerEffectFill(sel, fill, 0, true));

Inner glow​

Applies a glow effect inside the edges of the layer — uses colour, radius, opacity, and intensity.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { RGBA8 } = require('/colours.js');
const { Selection } = require('/selections.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (!node) { console.log('Select something first.'); }
else {
const sel = Selection.create(doc, node);

doc.executeCommand(DocumentCommand.createSetInnerGlowLayerEffectColour(sel, RGBA8(255, 200, 0, 255), true)); // bright yellow
doc.executeCommand(DocumentCommand.createSetInnerGlowLayerEffectRadius(sel, 50, true)); // 50px radius
doc.executeCommand(DocumentCommand.createSetInnerGlowLayerEffectOpacity(sel, 1.0, true)); // full opacity
doc.executeCommand(DocumentCommand.createSetInnerGlowLayerEffectIntensity(sel, 1.0, true)); // full intensity
}

Gaussian blur (layer effect)​

A live blur effect applied to the entire layer — non-destructive and adjustable after application.

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

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (!node) { console.log('Select something first.'); }
else {
const sel = Selection.create(doc, node);

doc.executeCommand(DocumentCommand.createSetGaussianBlurLayerEffectRadius(sel, 30, true)); // 30px — clearly visible
doc.executeCommand(DocumentCommand.createSetGaussianBlurLayerEffectOpacity(sel, 1.0, true));
}

Outer glow​

A glow effect radiating outward from the layer edges — set colour, radius, opacity, and intensity.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (node) {
const sel = Selection.create(doc, node);
doc.executeCommand(DocumentCommand.createSetOuterGlowLayerEffectColour(sel, RGBA8(255, 230, 100, 255), true));
doc.executeCommand(DocumentCommand.createSetOuterGlowLayerEffectRadius(sel, 25, true));
doc.executeCommand(DocumentCommand.createSetOuterGlowLayerEffectOpacity(sel, 0.9, true));
doc.executeCommand(DocumentCommand.createSetOuterGlowLayerEffectIntensity(sel, 0.8, true));
console.log('Outer glow applied.');
}

Inner shadow​

Inner shadow uses an index (like outer shadow) and adds an intensity parameter.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (node) {
const sel = Selection.create(doc, node);
doc.executeCommand(DocumentCommand.createSetInnerShadowLayerEffectColour(sel, RGBA8(0, 0, 0, 200), 0, true));
doc.executeCommand(DocumentCommand.createSetInnerShadowLayerEffectOffsetAngle(sel, 8, 315, 0, true));
doc.executeCommand(DocumentCommand.createSetInnerShadowLayerEffectRadius(sel, 0, 15, true));
doc.executeCommand(DocumentCommand.createSetInnerShadowLayerEffectOpacity(sel, 0, 0.7, true));
doc.executeCommand(DocumentCommand.createSetInnerShadowLayerEffectIntensity(sel, 0, 0.5, true));
console.log('Inner shadow applied.');
}

Colour overlay​

Colour overlay tints the entire layer with a solid colour at a given blend mode.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (node) {
const sel = Selection.create(doc, node);
doc.executeCommand(DocumentCommand.createSetColourOverlayLayerEffectColour(sel, RGBA8(255, 0, 100, 255), 0, true));
doc.executeCommand(DocumentCommand.createSetColourOverlayLayerEffectOpacity(sel, 0, 0.5, true));
console.log('Colour overlay applied.');
}

Bevel and emboss​

Bevel and emboss adds a 3D lighting effect. Unlike indexed effects, there is only one bevel per layer.

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

const doc = Document.current;
const node = doc?.selection.nodes.first;
if (node) {
const sel = Selection.create(doc, node);
doc.executeCommand(DocumentCommand.createSetBevelEmbossLayerEffectRadius(sel, 10, true));
doc.executeCommand(DocumentCommand.createSetBevelEmbossLayerEffectDepth(sel, 2.0, true));
doc.executeCommand(DocumentCommand.createSetBevelEmbossLayerEffectOpacity(sel, 0.8, true));
doc.executeCommand(DocumentCommand.createSetBevelEmbossLayerEffectDirection(sel, 135, 45, true)); // azimuth, elevation
console.log('Bevel and emboss applied.');
}

Additional effect commands​

  • createSetAllLayerEffectsScaleWithObject(sel, scale) — toggle whether all effects on the node scale when the node is transformed
  • createSetOuterShadowLayerEffectKnocksOut(sel, knocksOut, index, enableIfDisabled) — toggle shadow knock-out per instance
  • createSetBevelEmbossLayerEffectAzimuth(sel, azimuth, enableIfDisabled) — set bevel light angle independently
  • createSetBevelEmbossLayerEffectElevation(sel, elevation, enableIfDisabled) — set bevel light height independently

Effect instance management​

Every stackable effect (OuterShadow, InnerShadow, OuterGlow, InnerGlow, BevelEmboss, PhongBevel, ColourOverlay, GradientOverlay, Outline, GaussianBlur) shares the same management pattern — duplicate to stack a second instance, remove one instance, move instances to reorder, and toggle blend mode / enabled / scale-with-object per effect:

// Snippet — assumes sel is a Selection
// Duplicate (stack a second instance)
doc.executeCommand(DocumentCommand.createDuplicateOuterShadowLayerEffect(sel));
// Remove one instance (by index)
doc.executeCommand(DocumentCommand.createRemoveInnerGlowLayerEffect(sel, 0));
// Reorder stacked instances
doc.executeCommand(DocumentCommand.createMoveColourOverlayLayerEffect(sel, 0, 1));
// Per-effect blend mode, enabled toggle, scale-with-object
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectBlendMode(sel, 'Multiply', 0, true));
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectEnabled(sel, true, 0, true));
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectScaleWithObject(sel, true, 0, true));

The createDuplicate*, createRemove* (beyond createRemoveAllLayerEffects), createMove*, and createSet*LayerEffectBlendMode/Enabled/ScaleWithObject families exist for all ten effects with identical signatures — substitute the effect name.


Remove all effects​

createRemoveAllLayerEffects clears every live effect from the selected node at once.

// Snippet — assumes doc and sel are already defined
doc.executeCommand(DocumentCommand.createRemoveAllLayerEffects(sel));
console.log('All effects removed.');

Gradient overlay​

Gradient overlay applies a gradient fill on top of the layer. Like ColourOverlay and Outline it is indexed — you can stack multiple gradient overlays.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { GradientFill } = require('/fills.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;

if (node) {
const sel = Selection.create(doc, node);

// Enable a gradient overlay at index 0 (creates it if not present)
const gf = GradientFill.createDefault(); // black-to-white linear gradient
doc.executeCommand(DocumentCommand.createSetGradientOverlayLayerEffectFill(sel, gf, true));

// Adjust blend mode and opacity
doc.executeCommand(DocumentCommand.createSetGradientOverlayLayerEffectBlendMode(sel, doc.blendMode, 0, true));
doc.executeCommand(DocumentCommand.createSetGradientOverlayLayerEffectOpacity(sel, 0.6, 0, true));

console.log('Gradient overlay applied.');
}

Remove a specific gradient overlay with createRemoveGradientOverlayLayerEffect(sel, index).

GradientOverlayLayerEffect.calculateTransform​

A static helper for computing the fill transform for gradient overlay effects:

// Snippet
const { GradientOverlayLayerEffect } = require('/layereffects.js');
const xf = GradientOverlayLayerEffect.calculateTransform(1, 1, 0, 0);
// scaleX, scaleY, transformX, transformY → returns a Transform

OutlineLayerEffect has the same static helper: OutlineLayerEffect.calculateTransform(scaleX, scaleY, transformX, transformY).


Phong bevel​

Phong bevel is a lighting-based 3D bevel effect distinct from the flat Bevel & Emboss. It adds ambient, diffuse, and specular light parameters for a more realistic surface simulation.

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;

if (node) {
const sel = Selection.create(doc, node);

// Radius (bevel depth in pixels), opacity
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectRadius(sel, 8, true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectOpacity(sel, 0.9, true));

// Lighting parameters
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectAmbient(sel, 0.2, true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectDiffuse(sel, 0.8, true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectShininess(sel, 40, true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectSpecular(sel, 0.5, true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectDepth(sel, 5.0, true));

// Ambient and specular colours
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectAmbientColour(sel, RGBA8(255, 255, 255, 255), true));
doc.executeCommand(DocumentCommand.createSetPhongBevelLayerEffectSpecularColour(sel, RGBA8(255, 255, 220, 255), true));

console.log('Phong bevel applied.');
}

Remove with createRemovePhongBevelLayerEffect(sel).


Adjustment layer parameter commands modify an existing adjustment layer. Adjustment layers can also be created programmatically via their *AdjustmentRasterNodeDefinition factory classes — see Lesson 17 — Raster Adjustments for the full workflow. The snippet below shows how to modify parameters on an existing node:

'use strict';
const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const Nodes = require('/nodes.js');

const doc = Document.current;
const node = doc?.selection.nodes.first;

if (!node) {
console.log('Select a Brightness/Contrast adjustment layer first.');
} else if (!(node instanceof Nodes.BrightnessContrastAdjustmentRasterNode)) {
console.log('Selected node is not a Brightness/Contrast adjustment layer.');
} else {
const sel = Selection.create(doc, node);
doc.executeCommand(DocumentCommand.createSetBrightnessContrastAdjustmentParameters(sel, {
brightness: 0.5, // -1.0 to 1.0
contrast: 0.4, // -1.0 to 1.0
isLinear: false
}));
console.log('Parameters set.');
}

Adjustment parameter commands require an adjustment node. Calling a parameter command on a shape, group, or doc.selection directly is a silent no-op. Always build the selection with Selection.create(doc, node) where node is the adjustment layer.


Destructive filters on raster nodes​

Destructive filters modify pixel data directly and cannot be undone without the undo stack. They only work on raster nodes — applying them to a shape or group is a silent no-op.

There are two sets of filter commands:

  • createGaussianBlurFilter(sel, params) — creates and applies the filter. Use this.
  • createSetGaussianBlurFilterParameters(sel, params) — sets parameters on an already existing filter. Same pattern as adjustment layers — silent no-op if no filter exists yet.
'use strict';
const { Document } = require('/document.js');
const { DocumentCommand, RasterSelectionLogicalOperation } = require('/commands.js');
const { HalftoneDotType, HalftoneScreenType } = require('affinity:dom');

const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
// createGaussianBlurFilter — creates and applies in one step
doc.executeCommand(DocumentCommand.createGaussianBlurFilter(doc.selection, { radius: 20 }));

doc.executeCommand(DocumentCommand.createVignetteFilter(doc.selection, {
radius: 0.6,
intensity: 0.8
}));

doc.executeCommand(DocumentCommand.createAddNoiseFilter(doc.selection, {
intensity: 0.3,
monochromatic: false
}));

// Halftone filter
doc.executeCommand(DocumentCommand.createHalftoneFilter(doc.selection, {
dotType: HalftoneDotType.Dot,
screenType: HalftoneScreenType.Frequency,
frequency: 20,
angle: 45,
dotSize: 0.5
}));

console.log('Destructive filters applied.');
}

Use create*Filter not createSet*FilterParameters to apply a new filter. The createSet* variants modify an existing filter and are silent no-ops if none exists yet.

More live-filter families​

The same add-then-edit pattern covers every filter family — createXFilter(sel, params) to apply, createSetXFilterParameters(sel, params) to edit an existing one:

FamilyEffect
BilateralBlurEdge-preserving blur
BoxBlurUniform box blur
MedianBlurMedian noise reduction
MaximumBlur / MinimumBlurMax/min (dilate/erode-style) blurs
MotionBlurDirectional motion blur
LensBlurAperture blur
FieldBlurSpatially-varying blur
DepthOfFieldDepth-based focus blur
DiffuseGlow / DiffuseSoft glow diffusion / grain diffusion
BloomHighlight bloom
ClarityLocal-contrast clarity
HighPass / UnsharpMaskSharpening filters
DenoiseNoise reduction
DustAndScratchDust/scratch removal
DefringeChromatic-fringe removal
VoronoiVoronoi cell pattern
PixelateMosaic pixelation
Ripple / Twirl / Spherical / PinchPunch / RadialBlurDistortions
ShadowsHighlightsFilter (not adjustment) version of shadows/highlights

Drawing scale & margins​

Read page geometry and drawing scale properties from the current spread and document preset.

'use strict';
const { Document } = require('/document.js');
const { DrawingScale } = require('/drawingscale.js');

const doc = Document.current;
const spread = doc.currentSpread;

// Margins — spread.marginsInterface returns undefined
// Use physicalRootPropertiesInterface for page geometry instead
const prpi = spread.physicalRootPropertiesInterface;
console.log('Page count:', prpi?.pageCount);
const box = prpi?.pageBoxInterface?.getPageBoundingBox(0);
console.log('Page box:', box);

// Drawing scale on the document preset
// IMPORTANT: pass a raw integer — NOT UnitType.* (those return undefined and cause INVALID_ARGS)
// 15 = Metres, 8 = Millimetres, 9 = Centimetres, 11 = Inches, 5 = Pixels
const defaults = DrawingScale.getDefaults(15); // 15 = Metres
console.log(`${defaults.length} standard metre scales available`);
for (const s of defaults.slice(0, 5)) {
console.log(' ', s.toString(false, true, false));
}

// Create a custom scale
const myScale = DrawingScale.createFromIntegers(1, 200, true);
console.log('My scale:', myScale.toString(false, true, false));

Raster brush properties​

The /rasterbrush module exposes raster brush dynamics. Access is via a brush handle from the application context (when a brush is active in the UI):

'use strict';
const { RasterBrush, BrushDynamic, BrushDynamicControllerType } = require('/rasterbrush.js');

// Once you have a brush handle (from app context):
// const brush = ...;

// All brush dynamic properties have .value, .variance, and .controllerType
// brush.size.value — base diameter
// brush.flow.value — flow rate (0–1)
// brush.opacity — brush opacity (0–1)
// brush.spacing — spacing between dabs
// brush.hardness.value — edge hardness
// brush.angle.value — brush angle
// brush.scatterX.value — X scatter
// brush.scatterY.value — Y scatter
// brush.hueShift.value — hue shift (HSL)
// brush.satShift.value — saturation shift
// brush.lumShift.value — luminance shift
// brush.wetEdges — wet edges on/off
// brush.blendMode — blend mode enum

RasterBrush.createDefault() is not implemented. The only way to obtain a RasterBrush instance is from a brush handle when the C++ layer provides one from tool state. There is no direct SDK API to get the currently active brush.


Key gotchas​

create*Filter not createSet*FilterParameters to apply a new filter. createSetGaussianBlurFilterParameters modifies an existing filter — silent no-op if none exists. Use createGaussianBlurFilter to create and apply in one step.

Adjustment layers are created via static factories, not new. Use *AdjustmentRasterNodeDefinition.create(params) or .createDefault() — instantiating nodes directly with new throws Invalid handle. See Lesson 17 for the full workflow.

Effect commands do not work inside CompoundCommandBuilder. Wrapping effect commands in a compound command is a silent no-op — the effect appears in the panel but doesn't render. Execute each effect command individually with doc.executeCommand.

Use Selection.create(doc, node) not doc.selection. Effect commands require a per-node selection built with Selection.create. Passing doc.selection directly silently does nothing.

The argument order is (sel, opacity, index, enableIfDisabled) — not (sel, index, opacity, enableIfDisabled). Passing the index value (0) as opacity makes the shadow fully transparent with no error or warning.

The index parameter. For effects that support multiple instances (shadows, outlines), always pass the index explicitly. Forget it and you'll address the wrong instance.

Check adjustment_ranges and filter_ranges docs in the SDK for valid parameter ranges before applying numeric values — out-of-range values may be clamped or rejected silently.

removeAllLayerEffects vs removing one. Use createRemoveOuterShadowLayerEffect(sel, index) to remove a specific effect; createRemoveAllLayerEffects(sel) removes everything.

enableIfDisabled: true for new effects. Without it, setting a property on a non-existent effect does nothing silently.


Exercises​

Exercise 12.1 — Shadow batch​

Apply a consistent drop shadow (6px offset, 45° angle, 8px blur, 70% opacity) to every selected node using a compound command.

Exercise 12.2 — Glow ring​

Apply an outer glow and an inner glow to selected nodes. Make the outer glow amber and the inner glow white.

Exercise 12.3 — Effect cleaner​

Write a script that removes all layer effects from every node in the document.

Exercise 12.4 — Adjustment stack​

Select an image node. Apply a Brightness/Contrast adjustment and a Vibrance adjustment in sequence.


SDK modules used in this lesson​

  • /commands — DocumentCommand, CompoundCommandBuilder
  • /layereffects — layer effect parameter types
  • /colours — RGBA8
  • /drawingscale — DrawingScale
  • /rasterbrush — RasterBrush
  • adjustment_ranges — valid parameter ranges for adjustments
  • filter_ranges — valid parameter ranges for filters

See also​



Example scripts​

12-diagnostic-shadow.js​

Download 12-diagnostic-shadow.js

12-diagnostic-shadow.js
'use strict';

// DIAGNOSTIC — Shadow command tester
// Lesson 11: Layer Effects
//
// Tests createSetOuterShadowLayerEffect with a properly constructed
// OuterShadowLayerEffect object — then shows per-property commands
// with verified argument orders as alternatives.
//
// Select a single object and run. Check the canvas after each log line.

const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');
const { OuterShadowLayerEffect } = require('/layereffects.js');

const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
const nodes = doc.selection.nodes;
if (nodes.isEmpty) { console.log('Select one object and run again.'); }
else {
const node = nodes.first;
const sel = Selection.create(doc, node);

console.log('Running shadow diagnostic...');

// Full effect setter approach — most reliable
const effect = OuterShadowLayerEffect.create();
effect.colour = RGBA8(255, 0, 0, 255); // bright red
effect.offset = 30; // 30px distance
effect.angle = 315 * Math.PI / 180; // 315 degrees = bottom-right (in radians)
effect.radius = 20; // 20px blur
effect.opacity = 1.0; // 100%
effect.enabled = true;

doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffect(sel, effect, 0));
console.log('1. Full effect setter applied — shadow should be clearly visible bottom-right');

// Per-property commands with verified argument orders (for reference)
// NOTE: these have inconsistent argument orders — use the full setter above instead
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectColour(sel, RGBA8(0, 255, 0, 255), 0, true));
console.log('2. Colour changed to green via per-property command');

// Opacity: (sel, value, index, enable) — value BEFORE index
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectOpacity(sel, 1.0, 0, true));
console.log('3. Opacity set to 100%% via per-property command — (sel, value, index, enable)');

// Radius: (sel, index, value, enable) — index BEFORE value
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectRadius(sel, 0, 20, true));
console.log('4. Radius set to 20px via per-property command — (sel, index, value, enable)');

// Offset+Angle: (sel, offset, angle, index, enable) — angle in RADIANS
doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffectOffsetAngle(sel, 30, 315 * Math.PI / 180, 0, true));
console.log('5. Offset+angle set via per-property command — angle in RADIANS (315deg = 5.498rad)');
}
}

12a-shadow-batch.js​

Download 12a-shadow-batch.js

12a-shadow-batch.js
'use strict';

// Example 11a — Shadow batch
// Lesson 11: Layer Effects
//
// Applies a consistent drop shadow to every selected node.
//
// Uses createSetOuterShadowLayerEffect with a properly constructed
// OuterShadowLayerEffect object — more reliable than individual
// property commands which have inconsistent argument orders.

const { Document } = require('/document.js');
const { DocumentCommand } = require('/commands.js');
const { Selection } = require('/selections.js');
const { RGBA8 } = require('/colours.js');
const { OuterShadowLayerEffect } = require('/layereffects.js');

const SHADOW_COLOUR = RGBA8(0, 0, 0, 180);
const SHADOW_OFFSET = 6; // pixels
const SHADOW_ANGLE = 315; // degrees (315 = bottom-right)
const SHADOW_RADIUS = 8; // blur radius in pixels
const SHADOW_OPACITY = 0.7; // 0.0–1.0

const doc = Document.current;
if (!doc) { console.log('No document open.'); }
else {
const nodes = doc.selection.nodes;
if (nodes.isEmpty) {
console.log('Select some objects and run again.');
} else {
const angleRad = SHADOW_ANGLE * Math.PI / 180;

for (const node of nodes) {
const sel = Selection.create(doc, node);
const effect = OuterShadowLayerEffect.create();

effect.colour = SHADOW_COLOUR;
effect.offset = SHADOW_OFFSET;
effect.angle = angleRad;
effect.radius = SHADOW_RADIUS;
effect.opacity = SHADOW_OPACITY;
effect.enabled = true;

doc.executeCommand(DocumentCommand.createSetOuterShadowLayerEffect(sel, effect, 0));
}

console.log(`Drop shadow applied to ${nodes.length} node(s).`);
}
}