New in Sketch 2025.3

Model Context Protocol

Sketch now ships with a built-in MCP server that enables connected AI clients to write and execute JavaScript code directly in Sketch.

This server transforms Sketch into an interactive AI-driven REPL environment for SketchAPI scripts. Whether you want AI as a sidekick for writing and troubleshooting Sketch plugins, or as a fully autonomous agent that can iterate on code independently, the MCP server enables both workflows.

Image Background Removal

Our new on-device image background removal feature powered by Apple’s Vision framework is now available via the asynchronous JavaScript API:

anImageLayer.removeBackground((error) => {
  if (error) {
    console.error(`Background removal failed: ${error}`)
  }
})

// or, in case of multiple image layers:

const { Image } = require('sketch')
Image.removeBackgroundFromLayers(imageLayers, (error) => {
  if (error) {
    console.error(
      `Background removal failed for ${imageLayers.length} images: ${error}`,
    )
  }
})

Both methods accept an optional people flag that switches the underlying ML model to one optimized for images with people:

anImageLayer.removeBackground({ people: true }, (error) => {
  // ...
})

Image.removeBackgroundFromLayers(imageLayers, { people: true }, (error) => {
  // ...
})

Wrapping Stacks

You can now control whether the contents of a particular Stack Layout should wrap when their combined height (for vertical stacks) or width (for horizontal stacks) exceeds the Stack’s fixed dimensions:

const { Group, StackLayout, FlexSizing } = require('sketch')

// for new Stacks
const stack = new Group.Frame({
  stackLayout: {
    direction: StackLayout.Direction.Row,
    wraps: true,
  },
  horizontalSizing: FlexSizing.Fixed,
})

// or, for existing Stacks
container.stackLayout.wraps = true

You can also choose how the wrapped content should be aligned on the cross axis:

container.stackLayout.alignContent = StackLayout.AlignContent.End

and specify the gap between wrapped lines:

container.stackLayout.crossAxisGap = 30

Symbols

Back in Sketch 94, we introduced an option to expand Symbol instances right in the Layer List to reveal all overridable layers inside – so you could select any of them and use the Overrides panel in the Inspector to quickly change overrides for those specific layers.

As for the JS API, working with layers inside Symbol Instances has always been a challenge. Symbol Instances don’t contain any layers – their parent Symbol Sources do; in fact, instances are simply references to those Sources with additional metadata like Overrides attached. Because of those Overrides, iterating through a Symbol Source’s layers while mapping them to an actual Symbol Instance configuration is error-prone.

Looking inside Symbol Instances

To solve this problem, Sketch 2025.3 introduces SymbolInstance.expandedLayers – a read-only collection of overridable layers inside the given Symbol Instance:

symbolInstance.expandedLayers.forEach((layer) => {
  const frame = layer.frame
  const isHidden = layer.hidden

  if (layer.type === sketch.Types.Text) {
    console.log(layer.text === 'This text is overridden') // true
  }

  layer.layers?.forEach((grandchild) => {
    // ...
  })

  if (layer.isNestedSymbol) {
    const symbolSource = document.getSymbolMasterWithID(layer.symbolId)
    // ...
  }
})

Nested Symbols are represented as normal Groups with a couple of special properties: isNestedSymbol and symbolId – to help differentiate them from regular layer containers.

Modifying Symbol Instances

Overrides are still the only way to make changes to Symbol Instances. To access an instance’s overrides while iterating its expandedLayers collection, use the new helper method SymbolInstance.overridesForExpandedLayer():

symbolInstance.expandedLayers.forEach((layer) => {
  findAndUpdateText(layer, symbolInstance)
})

function findAndUpdateText(layer, parentInstance) {
  if (layer.hidden) return

  if (layer.name === 'the one') {
    const overrides = parentInstance.overridesForExpandedLayer(layer)
    const textOverride = overrides.find(o => o.property === 'stringValue')
    textOverride?.value = 'New text'

    // (optional) dive in recursively
    layer.layers?.forEach((grandchild) =>
      findAndUpdateText(grandchild, parentInstance),
    )
  }
}

Color Variables

We’d like to give a huge thank you to Kevin Gutowski for his remarkable “What’s up with Color Variables?” post that’s been a tremendous source of inspiration for us 🙌

Color Variables (aka Swatches) are now fully represented in the JS API:

  1. A new swatch property has been added next to the color property on Fills, Borders, Shadows, GradientStops, Tints, Texts, and Overrides:

    // On individual style parts
    style.fills[0].swatch
    style.borders[0].swatch
    style.shadows[0].swatch
    // including Group Tints
    group.style.tint.swatch
    
    // On gradient stops
    style.fills[0].gradient.stops[0].swatch
    
    // On Texts
    textLayer.style.textSwatch
    
    // On Overrides
    if (override.colorOverride) {
      override.swatchValue
      override.defaultSwatchValue
    }
    
  2. Swatch.color is now a writable property:

    swatch.color = '#ff6600ff'
    // Note: all colors referencing this swatch will be auto-updated to this value
    
  3. Added Swatch.getLibrary() and Swatch.syncWithLibrary():

    const sourceLibraryName = swatch.getLibrary()?.name
    
    swatch.syncWithLibrary()
    

Color Variables 101

As a quick reminder, here’s an overview of the Color Variables API:

// A Color Variable must be added to a document before its first use:
document.swatches.append({
  name: 'Safety Orange',
  color: '#ff6600ff',
})
const swatch = document.swatches[0]

// Now, wherever you'd normally specify a plain color string, you may provide
// a special color object that's tied to that Color Variable and will
// automatically update itself when the latter changes:
layer.style.fills = [
  {
    fillType: 'Color',
    color: swatch.referencingColor,
  },
]
// Alternatively, pass a swatch object directly:
layer.style.fills = [
  {
    fillType: 'Color',
    swatch: swatch,
  },
]

// You may then go ahead and change the actual color value of this Swatch:
swatch.color = '#ffffffff'
// which will update all references to this Swatch throughout the document:
console.log(layer.style.fills[0].color === '#ffffffff') // true

In addition to local Color Variables, Sketch also supports Color Variables imported from Libraries:

const sketch = require('sketch')

// First, get a list of all Swatches available from a given library:
const document = sketch.getSelectedDocument()
const library = sketch.Library.getLibraryForDocumentAtPath(
  'ACME Design System.sketch',
)
const allLibrarySwatches =
  library.getImportableSwatchReferencesForDocument(document)

// Then find the one you need and `import()` it:
const importable = allLibrarySwatches.find(
  (s) => s.name === 'Unsafety Orange',
)
const importedSwatch = importable.import()

// That's it: you may now use this imported Swatch like a regular one
layer.style.fills = [
  {
    fillType: 'Color',
    swatch: importedSwatch,
  },
]

// The only difference is that it has its source library information attached:
console.log(importedSwatch.getLibrary()?.name === 'ACME Design System') // true
// and can be updated whenever there are changes in the source library:
importedSwatch.syncWithLibrary()

Other Changes and Bugfixes

  • Added StackLayout.AlignItems.Stretch to mimic the new “Stretch vertically/horizontally” option for aligning items within Stacks.
  • We’ve deprecated Blur.isCustomGlass as Auto Glass mode doesn’t exist anymore in Sketch. This property will always return true from now on.
  • Fixed an issue where text layers created via new Text({ style: ... }) would have an unexpected default border applied to them.
  • Fixed SharedStyle.styleType to return an actual effective style type instead of a “SharedStyle” string literal.
  • Fixed StackLayout.gap to accept non-integer values.
  • Fixed an issue where find('Frame') and find('Graphic') wouldn’t match any Symbol Masters.