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() or Group.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 and Group.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 report true 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 from 0.0 (for -100%) to 2.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 a ShapePath 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 to sketch.export():

    sketch.export(layer, {
      output: '/path/to/output/folder',
      exportFormats: layer.exportFormats,
    })
    

    These new exportFormats take precedence over the formats and scales options.

  • Custom ExportFormata 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 make sketch.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 on Document.selectedLayers and Page.selectedLayers arrays:

    const selectedTexts = page.selectedLayers.filter((layer) => {
      return layer.type === sketch.Types.Text
    })
    

Prototypes

  • Fixed Flow.target to actually return Flow.BackTarget instead of undefined for “back” actions.