New in Sketch 2025.2
We’ve released Sketch 2025.2 Barcelona on August 7, 2025.
Stacks
Use the new stackLayout
property of a Group
to find out whether a given container is a Stack:
if (group.stackLayout) {
// This is a Stack
}
Inspect this property further to learn about this particular Stack Layout:
const { StackLayout } = require('sketch/dom')
const { direction, alignItems, ...other } = group.stackLayout
if (group.stackLayout.justifyContent === StackLayout.JustifyContent.End) {
// ...
}
Creating Stacks
To create a brand new container with a Stack Layout, pass a stackLayout
object to a Group
during its initialization:
const stack = new Group({
stackLayout: {
direction: StackLayout.Direction.Column,
alignItems: StackLayout.AlignItems.Center,
gap: 120,
},
layers: [
// ...
],
})
Converting containers to Stacks
In order to convert an existing container to a Stack, pass a StackLayout
to its stackLayout
property:
let container = ... // a layer group
container.stackLayout = {
padding: { top: 50, bottom: 30 },
}
Manipulating Stack Items
When it comes to adding or removing items from a Stack, use the usual Group.layers
collection:
let stack = new Group({
stackLayout: { padding: 10 },
layers: [
// ...
],
})
let layer = new Text()
stack.layers.push(layer)
stack.layers.splice(0, 2)
Each individual Stack item has 2 flags that affect its participation in a Stack Layout: ignoresStackLayout
and preservesSpaceInStackLayoutWhenHidden
// Controls whether this layer ignores the enclosing
// Stack Layout properties when positioning itself
stack.layers[0].ignoresStackLayout = true
const stack = new Group({
stackLayout: { padding: 10 },
layers: [
new Text({
// Controls whether a Stack should keep a layer's space
// even when it's hidden
preservesSpaceInStackLayoutWhenHidden: true,
}),
],
})
Removing Stack Layout from containers
Set the stackLayout
value to null
to remove the Stack Layout from the given Stack and effectively convert it to a regular layer container:
container.stackLayout = null
Corner Styling
Use the new Style.corners
property to access corner styling of a particular Layer:
const { Style } = require('sketch/dom')
layer.style.corners.style = Style.CornerStyle.Smooth
// `radii` specifies corner radius clockwise (starting at top-left)
// for rectangular layers or one for points on a shape path.
// Values are repeated until they fit the required points
layer.style.corners.radii = 10
layer.style.corners.radii = [10]
layer.style.corners.radii = [10, 20, 30]
if (layer.style.corners.hasRadii) {
console.log('This layer has a non-zero corner radius')
}
Corners.radiusAt()
returns the radius of a layer’s corner at the given index, iterating any previously set radii values as many times as necessary to find a matching value.
For instance, let’s say we have a shape with 8 corners. We can only set radii for the first two:
layer.style.corners.radii = [10, 20]
and let Sketch interpolate values for the other corners from the two set:
const radius = layer.style.corners.radiusAt(7)
console.log(`Radius no.7 is ${radius} as well`)
// 👉 Radius no.7 is 20 as well
Concentric Corners
A concentric corner follows the curvature of its ancestor Frame that also has corner radii. This may not be the immediate parent Frame but also one higher up. The curvature is followed by taking the parent corner radius into account, any padding it has and the layer’s distance to that Frame.
For instance, let’s create a Frame and set its radii to 20
:
const frame = new Group({
groupBehavior: GroupBehavior.Frame,
frame: new Rectangle(0, 0, 100, 100),
style: {
corners: { radii: 20 },
},
verticalSizing: FlexSizing.Fixed,
horizontalSizing: FlexSizing.Fixed,
})
Now insert a shape with concentric corners into this Frame with a uniform padding of 10
:
const shape = new ShapePath({
style: {
fills: [{ color: '#ffaa00' }],
corners: { concentric: true },
},
frame: new Rectangle(10, 10, 80, 80),
verticalSizing: FlexSizing.Relative,
horizontalSizing: FlexSizing.Relative,
})
frame.layers.push(shape)
Inspecting the shape’s corner radius we’ll see that it’s been automatically calculated to be 10
:
console.log(`Auto corner radius: ${layer.style.corners.radii}`)
// 👉 Auto corner radius: [10]
Now let’s shrink the parent Frame and see the shape’s corner radius being increased accordingly:
parent.frame = new Rectangle(0, 0, 80, 80)
console.log(`New corner radius: ${layer.style.corners.radii}`)
// 👉 New corner radius: [12]
Concentric Corners and Corner Styles
Setting the concentric
flag on layer’s corners switches them to Style.CornerStyle.Auto
mode:
layer.style.corners.concentric = true
const isAutoCorners = layer.style.corners.style === Style.CornerStyle.Auto
console.log(`Auto mode? ${isAutoCorners}`)
// 👉 Auto mode? true
Assigning values to any other property after that will switch the concentric flag back to false
:
layer.style.corners.radii = 10
// 👉 `corners.style` is reset to `Style.CornerStyle.Rounded`
// 👉 `corners.concentric` is reset to `false`
Progressive Blur
Use the new Blur.progressive
and Blur.gradient
properties to access a gradient of a particular Progressive Blur:
const { Style } = require('sketch/dom')
const style = ...
const blur = style.blurs[0]
if (blur.progressive) {
// All properties of a Gradient apply here as well:
console.log(`${blur.gradient.from} => ${blur.gradient.to}`)
if (blur.gradient.gradientType === Style.GradientType.Linear) {
// ...
}
}
We’ve also added a new property to GradientStops: GradientStop.alpha
to streamline reading blur gradient steps as they’re solely based on the alpha channel:
const blurProgression = blur.gradient.stops.map((stop) => stop.alpha)
// [0.3, 0.62079, 1.0]
Masks
We’ve expanded the JS API to let you work with Masks: Shapes and ShapePaths used to hide or show parts of other Layers.
To check if a certain layer is masked, inspect its closestMaskingLayer
property:
const maskShape = layer.closestMaskingLayer
if (maskShape) {
// the layer is masked by `maskShape`
}
In case of multiple masks, keep calling
closestMaskingLayer
on the returned value until there’s no masks left in the chain:layer.closestMaskingLayer?.closestMaskingLayer?.closestMaskingLayer // etc
To check whether a layer has the “Ignores Underlying Mask” flag enabled, effectively breaking a mask chain, refer to the Layer.breaksMaskChain
property:
if (layer.breaksMaskChain) {
// ...
}
A layer with the breaksMaskChain
flag set will automatically opt all layers above it out of masking as well.
Finally, to mark a certain Shape as a mask for its sibling layers:
shape.masksSiblings = true
shape.maskMode = Layer.MaskMode.Outline
sketchtool
-
Adds a
sketchtool run-script
command that enables quick code execution without needing to have a full plugin bundle on disk:sketchtool run-script "console.log(context.document.zoomValue())"
-
Reinstates the
sketchtool run
command, which became unavailable in 2025.1.
Other Notable API Changes
Frames and Graphics
-
We’ve added
Page.canvasLevelFrames
which may come in handy if you need to iterate all top-level Frames (that would be considered Artboards in previous versions of Sketch) on a given page:page.canvasLevelFrames.forEach((frame) => { // ✅ Treat this `frame` as an Artboard })
-
In order to create a Frame or a Graphic container, use one of the two new
Group
convenience constructors:Group.Frame()
orGroup.Graphic()
:const { Group } = require('sketch/dom') let frame = new Group.Frame({ name: 'My Frame' }) let graphic = new Group.Graphic({ layers: [{ type: 'Text', text: 'Hello there' }], })
-
To differentiate between regular layer groups, frames and graphic containers, use
Group.isFrame
andGroup.isGraphicFrame
properties:if (group.isFrame) { // ✅ This is a Frame OR a Graphic } if (group.isGraphicFrame) { // ✅ This is a Graphic }
ℹ️ Since Graphics are just a special kind of Frames,
Group.isFrame
will reporttrue
for both of them.
find()
-ing Frames
-
Use the
Group
type predicate to find all regular layer Groups as well as nested Frames and Graphics:const { find } = require('sketch') const containers = find(`Group`)
-
Use the
Frame
type predicate to find all Frames – nested and top-level. Note that the results will include Graphics as well:const frames = find(`Frame`)
-
Use the
Graphic
type predicate to find all Graphics – nested or top-level:const graphics = find(`Graphic`)
-
And finally use
Page.canvasLevelFrames
to find only top-level Frames and Graphics:page.canvasLevelFrames.forEach((frame) => { // ... })
Overrides
-
Fixed the
Override.value
property which used to return an internal description of a color override, which is useless for the most part:console.log(override.value) // 👉 "<MSImmutableColor: 0x348ddbb50>(68957)" // or, if you're lucky: // 👉 "(r:0.792156862745098 g:0.996078431372549 b:0.7294117647058823 a:0.7450980392156863)"
Now it returns a proper RGBA CSS color string:
print(override.value) // 👉 '#cafebabe'
-
Speaking of color overrides, there’s new
Override.colorOverride
flag for your convenience:if (override.colorOverride) { // ... }
-
And finally, we’ve introduced
Override.defaultValue
which always returns the default value for the given override point even if it was overridden previously:console.log(override.defaultValue)
Style
-
Added
Style.tint
property to control Group Tints:group.style.tint = { color: '#FFA500ff', }
-
Added
Style.progressiveAlpha
property to control Progressive Alpha:const alphaGradient = layer.style.progressiveAlpha if (alphaGradient) { alphaGradient.stops.forEach((stop) => { console.log(`Progress: ${stop.alpha}`) }) }
-
Added writable
blendingMode
property for all individual Style components: Fills, Borders, and Shadows:style.fill[0].blendingMode = Style.BlendingMode.Difference
-
Added two missing blending mode values to
Style.BlendingMode
:"Plus Darker"
and"Plus Lighter"
. -
Added the
Blur.saturation
property for Background blurs with values ranging from0.0
(for-100%
) to2.0
(for+100%
):const style = new Style({ blurs: [ { blurType: Style.BlurType.Background, saturation: 1.0, }, ], })
-
Fixed the
Style.blurs
property setter that used to fail with an “undefined is not an object” error in Sketch 2025.1:// ✅ works now layer.style.blurs = [ { blurType: Style.BlurType.Motion, }, ]
Shapes
-
Added
ShapePath.edited
property that tells whether aShapePath
has been edited or not:if (oval.edited) { // This may not be an Oval anymore }
Images
-
Added
ImageData.base64
property for quick access to base64-encoded image data:const imageAsString = imageLayer.image.base64
sketch.export()
-
Now you can pass Layer’s
exportFormats
directly tosketch.export()
:sketch.export(layer, { output: '/path/to/output/folder', exportFormats: layer.exportFormats, })
These new
exportFormats
take precedence over theformats
andscales
options. -
Custom
ExportFormat
a are supported as well:sketch.export(object, { output: '/path/to/output/folder', exportFormats: [ { size: '150w', fileFormat: 'png', }, { size: '2x', fileFormat: 'webp', }, ], })
Passing an empty array as
exportFormats
will makesketch.export()
fall back to its default options:PNG
,1x
, and no prefix/suffix:sketch.export(layer, { output: '/path/to/output/folder', exportFormats: [], }) // Exports "layer.png"
sketch.find()
-
Added an option for case-insensitive
name
search:// ~= const matches = find(`[name~="full case insensitive match"]`) // ~*= const matches = find(`[name~*="case insensitive substring"]`)
-
Added an option for including the
container
layer into search results if it matches the given query itself:const resultsIncludingContainer = sketch.find('...', container, { inclusive: true, })
One important use case for this option is “find in selection” where selection could be a combination of currently selected layers and their descendants.
Selection
-
We’ve made the
Array.filter
method available onDocument.selectedLayers
andPage.selectedLayers
arrays:const selectedTexts = page.selectedLayers.filter((layer) => { return layer.type === sketch.Types.Text })
Prototypes
- Fixed
Flow.target
to actually returnFlow.BackTarget
instead ofundefined
for “back” actions.