# New in Sketch 2026.1 ## agents.txt You may now point your AI agents to the following URLs serving up-to-date SketchAPI documentation as plain text: - [`llms.txt`](/reference/api/llms.txt) – a lightweight index page with links to individual documentation sections - [`llms-full.txt`](/reference/api/llms-full.txt) – the entire SketchAPI documentation on a single page - [`/plugins/updates/llms.txt`](/plugins/updates/llms.txt) – the list of recent changes and additions to SketchAPI; useful if you want to simply refresh your agent's existing SketchAPI skills ## Independent borders You can now override thickness for each individual side of a border for rectangles, Frames, and Graphics: ```js const border = layer.style.borders[0] border.thickness = 10 border.sides.left = 5 console.log(border.sides) // { // left: 5, // top: 10, // right: 10, // bottom: 10 // } border.sides = { left: 25, right: 30, } console.log(border.sides) // { // left: 25, // top: 10, // right: 30, // bottom: 10 // } ``` To check whether the given border has per-side thickness set, use `Border.hasIndividualSides` property: ```js if (border.hasIndividualSides) { console.log(`This border mixed thickness`) } ``` To reset the given border to a uniform thickness, assign `null` to its `sides` property: ```js border.sides = null // Note: `Border.thickness` will be set to the max individual side thickness console.log(border.thickness) // 30 console.log(border.hasIndividualSides) // false ``` ## Stacks Applying a `StackLayout` to a `Group` now triggers an immediate layout update, ensuring the `Group` has up-to-date geometry from the start: ```js const group = new Group.Frame({ layers: [ new Shape({ frame: new Rectangle(0, 0, 100, 100), }), ], stackLayout: { padding: 10, }, }) console.log(group.frame) // => (0, 0, 120, 120) -- paddings included ✅ ``` The layout update occurs automatically each time a `StackLayout` is modified: ```js group.stackLayout.padding = 0 console.log(group.frame) // => (0, 0, 100, 100) -- zero paddings ✅ ``` ### Added `StackLayout.apply()` In some cases – such as after making changes to individual stack items – you may still need to trigger a manual stack layout update. Use the new `StackLayout.apply()` method for that: ```js // Resizing a stack item directly group.layers[0].frame.width = 1000 console.log(group.frame) // => (0, 0, 120, 120) -- the new item width isn't accounted for ❌ group.stackLayout.apply() console.log(group.frame) // => (0, 0, 1020, 120) -- the stack geometry is now up to date ✅ ``` ### Also in Stacks - Fixed an issue where assigning a `StackLayout` to a `Group` didn't reset its cross-axis sizing to `Fit`, preventing the stack from sizing itself correctly in that direction. - Fixed an issue where assigning a new padding-less `StackLayout` to a `Group` (as well as removing the existing `StackLayout` entirely) failed to remove the stack's existing padding. ## DataSupplier Sketch can now import image files from the `Contents/Resources` folder inside a plugin bundle when those image files are referenced via a relative path in the structured data provided via the `DataSupplier` API: ```js function onProvideData(dataContext) { // Assuming the following plugin bundle structure: // . // └── Contents // ├── Resources // │   └── images // │   ├── one.png // │   ├── three.png // │   └── two.png // └── Sketch // ├── DataProvider.js // └── manifest.json DataSupplier.supplyData(dataContext.data.key, { 'Card 1': { title: 'One', image: 'images/one.png', }, 'Card 2': { title: 'Two', image: 'images/two.png', }, 'Card 3': { title: 'Three', image: 'images/three.png', }, }) } ``` ## Other Changes and Bugfixes - Added `Document.zoomToFitCanvas()`, which adjusts the zoom level and scroll position as needed to fit all layers of the current page in the canvas viewport. - Added the `Style.Corners.smoothing` property for fine-grained control over corner smoothing: ```js const { Style } = require('sketch') layer.style.corners.style = Style.CornerStyle.Smooth layer.style.corners.smoothing = 0.6 ``` - Added the `Layer.nameIsFixed` property to prevent Sketch from auto-updating a layer's name, which often affects text layers. - Added `Group.clipsContents` for supported containers: Frames, Graphics, and Symbol Masters. - Added support for direct Array-like access and enumeration for `Document.selectedLayers` and `Page.selectedLayers` objects: ```js const layer = document.selectedLayers[0] const layer = page.selectedLayers[6] for (const layer of document.selectedLayers) { console.log(`${layer.name}`) } ``` - Fixed `Group.isFrame` and `Group.isGraphicFrame` for Groups that don't have an explicit Frame or Graphic group behavior, but still act like one. - Deprecated `Group.groupBehavior:getter` in favor of `Group.isFrame` and `Group.isGraphicFrame`. - Changing `Layer.name` now automatically sets its `nameIsFixed` to `true`. - `ImageData.base64` is no longer included in `console.log()` and `Layer.toJSON()` output when printing image layers. - Removed the legacy Objective-C APIs for installing accessory header/footer views in the Sketch sidebar: - `-[MSMainSplitViewController insertAccessoryViewAtopCanvas:constrainingHeight:]` - `-[MSMainSplitViewController insertAccessoryViewBeneathCanvas:constrainingHeight:]` - `-[MSMainSplitViewController removeAccessoryViewAtopCanvas:]` - `-[MSMainSplitViewController removeAccessoryViewBeneathCanvas:]` If your plugin uses these APIs to extend Sketch UI, please reach out via the [Community Forum](https://forum.sketch.com/c/plugins-integrations/22) or email [developer@sketch.com](mailto:developer@sketch.com), so we can suggest the right replacement for your use case. - Made changes to the following deprecated Objective-C APIs: | Removed | Replacement | | --- | --- | | `-[MSExporter exporterForRequest:colorSpace:driver:]` | `-[MSExporter exporterForRequest:colorSpace:options:]` | | `-[MSExporer data]` | `-[MSExporter dataAndReturnError:]` | | `-[MSLayer duplicate]` | [`Layer.duplicate()`](/reference/api/#duplicate-the-layer) | | `-[MSLayer select:byExpandingSelection:]` | [`Layer.selected`](/reference/api/#layer) | | `-[MSSymbolInstance overrides]` | [`SymbolInstance.overrides`](/reference/api/#symbol-instance) | | `-[MSDocumentData allLayerStyles]` | [`Document.sharedLayerStyles`](/reference/api/#document) | | `-[MSDocumentData allTextStyles]` | [`Document.sharedTextStyles`](/reference/api/#document) | - Removed `old` and `new` keys from the [`TextChanged`](/reference/action/textchanged/) action context. # New in Sketch 2025.3 ## Model Context Protocol Sketch now ships with a built-in [MCP server](https://www.sketch.com/redirect/app/mcp) 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](https://www.sketch.com/docs/designing/images) powered by Apple's Vision framework is now available via the asynchronous JavaScript API: ```js 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: ```js 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: ```js 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: ```js container.stackLayout.alignContent = StackLayout.AlignContent.End ``` and specify the gap between wrapped lines: ```js 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: ```js 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 `Group`s 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()`: ```js 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](http://kevingutowski.com) for his remarkable [_"What's up with Color Variables?"_](https://web.archive.org/web/20220527015911/https://sketchplugins.com/d/2205-js-api-guide-whats-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: ```js // 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: ```js swatch.color = '#ff6600ff' // Note: all colors referencing this swatch will be auto-updated to this value ``` 3. Added `Swatch.getLibrary()` and `Swatch.syncWithLibrary()`: ```js const sourceLibraryName = swatch.getLibrary()?.name swatch.syncWithLibrary() ``` > ### Color Variables 101 > > As a quick reminder, here's an overview of the Color Variables API: > > ```js > // 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: > > ```js > 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. # New in Sketch 2025.2.2 We’ve released Sketch [2025.2.2 Barcelona](https://www.sketch.com/changelog/2025-2-2/) on September 15, 2025. ## Glass Use the new `Blur` properties to access information about a particular [Glass](/reference/api/#glass) effect: ```js if (blur.blurType === sketch.Style.BlurType.Glass) { const isAutoGlass = !blur.isCustomGlass if (isAutoGlass) { console.log(`auto glass`) } else { // The Glass parameters could be adjusted as follows // to match user-visible values in the Inspector console.log(`Blur: ${blur.radius}`) console.log(`Distortion: ${blur.distortion * 100}`) console.log(`Depth: ${blur.depth * 100}`) console.log(`Chromatic Aberration: ${blur.chromaticAberration * 100}`) console.log(`Brightness: ${(blur.brightness - 1) * 100}`) console.log(`Saturation: ${(blur.saturation - 1) * 100}`) console.log(`Specular Highlights: ${blur.hasSpecularHighlights}`) } } ``` # New in Sketch 2025.2 We’ve released Sketch [2025.2 Barcelona](https://www.sketch.com/changelog/barcelona/) on August 7, 2025. ## Stacks Use the new `stackLayout` property of a [`Group`](#Group) to find out whether a given container is a Stack: ```js if (group.stackLayout) { // This is a Stack } ``` Inspect this property further to learn about this particular Stack Layout: ```js 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: ```js 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: ```js 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: ```js 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` ```js // 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: ```js container.stackLayout = null ``` ## Corner Styling Use the new `Style.corners` property to access corner styling of a particular Layer: ```js 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: ```js layer.style.corners.radii = [10, 20] ``` and let Sketch interpolate values for the other corners from the two set: ```js 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`: ```js 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`: ```js 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`: ```js 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: ```js 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: ```js 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`: ```js 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: ```js 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](#gradient-stops): `GradientStop.alpha` to streamline reading blur gradient steps as they're solely based on the alpha channel: ```js 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](#shape) and [ShapePaths](#shapepath) used to hide or show parts of other Layers. To check if a certain layer is masked, inspect its `closestMaskingLayer` property: ```js 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: > > ```js > 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: ```js 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: ```js 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: ```sh 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`](#page) 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: ```js 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()`: ```js 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`](#group) and [`Group.isGraphicFrame`](#group) properties: ```js 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`](#group) will report `true` for both of them. #### `find()`-ing Frames - Use the `Group` type predicate to find all regular layer [Groups](#group) as well as _nested_ [Frames](#working-with-frames-and-graphics) and [Graphics](#working-with-frames-and-graphics): ```js 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: ```js const frames = find(`Frame`) ``` - Use the `Graphic` type predicate to find all Graphics – nested or top-level: ```js const graphics = find(`Graphic`) ``` - And finally use `Page.canvasLevelFrames` to find _only top-level_ Frames and Graphics: ```js 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: ```js console.log(override.value) // 👉 "(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: ```js print(override.value) // 👉 '#cafebabe' ``` - Speaking of color overrides, there's new `Override.colorOverride` flag for your convenience: ```js 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: ```js console.log(override.defaultValue) ``` ### Style - Added `Style.tint` property to control Group [Tints](https://www.sketch.com/docs/symbols-and-styles/styling/tints/): ```js group.style.tint = { color: '#FFA500ff', } ``` - Added `Style.progressiveAlpha` property to control [Progressive Alpha](#): ```js 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: ```js 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%`): ```js 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: ```js // ✅ works now layer.style.blurs = [ { blurType: Style.BlurType.Motion, }, ] ``` ### Shapes - Added `ShapePath.edited` property that tells whether a `ShapePath` has been edited or not: ```js if (oval.edited) { // This may not be an Oval anymore } ``` ### Images - Added `ImageData.base64` property for quick access to base64-encoded image data: ```js const imageAsString = imageLayer.image.base64 ``` ### `sketch.export()` - Now you can pass Layer's `exportFormats` directly to `sketch.export()`: ```js sketch.export(layer, { output: '/path/to/output/folder', exportFormats: layer.exportFormats, }) ``` > These new `exportFormats` **take precedence** over the `formats` and `scales` options. - Custom `ExportFormat`a are supported as well: ```js 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: > > ```js > sketch.export(layer, { > output: '/path/to/output/folder', > exportFormats: [], > }) > > // Exports "layer.png" > ``` ### `sketch.find()` - Added an option for case-insensitive `name` search: ```js // ~= 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: ```js 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: ```js 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. # New in Sketch 2025.1 We’ve released Sketch [2025.1 Athens](https://www.sketch.com/changelog/athens/) on May 27, 2025. Below you will find an overview of the API changes made in this release, and also a few tips and tricks to get you started with the new features. ## Migrating from Artboards to Frames and Graphics In Athens two new types of layer _containers_ were introduced: [Frames and Graphics](https://www.sketch.com/blog/frames/) — which replaced Artboards. Since all _containers_ are now mere layer groups with a specific layout behavior, in order to distinguish one from another don't rely on just their `type` alone, but inspect the new `groupBehavior` property instead: ```js const { Group, GroupBehavior } = require('sketch/dom'); const group = // ... if (group.groupBehavior === GroupBehavior.Frame) { // a Frame } else if (group.groupBehavior === GroupBehavior.Graphic) { // a Graphic } else if { // a regular layer group } ``` Note that all _top-level_ (i.e. lying directly on their parent page) Frame and Graphic containers will still report `Artboard` as their type for backwards compatibility: ```js const { sketch } = require('sketch/dom'); const topLevelContainer = // ... if (topLevelContainer.type === sketch.Types.Artboard) { // could be a top-level Frame or a Graphic, // or an Artboard (in previous versions of Sketch) } ``` In similar fashion, calling the `getParentArtboard()` function on a layer will return its enclosing _top-level_ container. ### Iterating Containers One major difference between legacy Artboards and new Frame and Graphic containers is that the latter can be _nested_. This presents a challenge of differentiating any _top-level_ containers (that might play the role of a standalone design components like individual UI screens) — from _nested_ containers, usually just grouping lower-level design elements. For instance, to find all _top-level_ containers on the given `Page`, you might iterate its `layers` array directly: ```js function findTopLevelContainersOnPage(page) { return page.layers.filter((layer) => layer.type === sketch.Types.Artboard) } ``` or take advantage of the `find` API: ```js function findTopLevelContainersOnPage(page) { return sketch.find('Artboard', page) } ``` In order to access the second-level containers, feel free to iterate the parent container’s `layers` array and so forth: ```js findTopLevelContainersOnPage(page).forEach((topLevelContainer) => { const secondLevelContainers = topLevelContainer.layers.filter((layer) => { return layer.type === sketch.Types.Group }) // ... }) ``` ### `sketch.find()` The `find` API is a great fit if you need to quickly iterate a large layer hierarchy to find a specific layer, or multiple layers matching specific queries: ```js // To find a top-level container with the given identifier in the whole document const id = 'XXX-YYY-ZZZ...' const container = sketch.find(`Artboard, #${id}`)[0] // To find all nested containers (children of the given one) matching the given name const marker = '[export]' const containers = sketch.find(`Group, [name*='${marker}']`, parentContainer) // To find all currently selected top-level containers on the given page const selection = sketch.find(`Artboard, [selected=true]`) ``` In case you want to include the parent container itself if it matches the given predicate, pass the `inclusive` flag: ```js // Will return all visible sublayers of `root`, including `root` itself if it's visible sketch.find('[hidden=false]', root, { inclusive: true }) ``` ### Sizing and Layout Inside new containers, layers now have [standardized sizing and pinning options](https://www.sketch.com/blog/frames/#working-within-frames). Accessing and modifying these options from a plugin is done via the following new APIs. #### Layer Sizing via `Layer.horizontalSizing` and `Layer.verticalSizing`. These properties can be set to either `Fixed`, `Fit`, `Fill`, or `Relative`: ```js const { FlexSizing } = require('sketch/dom') switch (layer.horizontalSizing) { case FlexSizing.Fixed: // The layer determines its own size along the horizontal axis, // but other properties may override it (see the article linked above) break case FlexSizing.Fit: // The layer hugs its children or contents along the horizontal axis. // Available for Stacks and text layers break case FlexSizing.Fill: // The layer fills the available horizontal space in the parent container. // Available to layers inside Stacks break case FlexSizing.Relative: // The layer is sized proportionally relative to its parent // along the horizontal axis. When its parent resizes, it does too break } layer.verticalSizing = FlexSizing.Relative ``` Note that not all FlexSizing options are available for all layers at all times — see ["Sizing options for frame contents"](https://www.sketch.com/blog/frames/#working-within-frames) for details. #### Layer Pinning via `Layer.horizontalPins`, `Layer.verticalPins`. These properties can each be set to either `None`, `Min`, `Max`, or `Both` — with _Min_ and _Max_ corresponding to either the left and right edges, or top and bottom edges depending on the chosen axis: ```js const { Pin } = require('sketch/dom') switch (layer.verticalPins) { case Pin.None: // neither top nor bottom edge is pinned break case Pin.Min: // the top edge is pinned break case Pin.Max: // the bottom edge is pinned break case Pin.Both: // both top and bottom edges are pinned break } ``` Both `horizontalPins` and `verticalPins` are [_bitmasks_](), which means that in order to query whether a _specific_ edge is pinned, you need to use the _bitwise_ AND operator — defined as **single** `&` in JavaScript (not to be confused with a **double** `&&` for the _logical_ AND): ```js if (layer.verticalPins & Pin.Min) { // The top edge is pinned // (the bottom edge might be pinned too, but we don't care) } ``` In order to pin edges yourself, you may either assign an entire new value for the desired axis: ```js // Leave only the right edge pinned // (this will pin the right edge *and* unpin the left one if pinned) layer.horizontalPins = Pin.Max ``` or you may toggle individual edges via the bitwise OR and AND operators – to add or remove certain pins respectively: ```js // Pin the left edge. Other already pinned edges remain intact: layer.horizontalPins |= Pin.Min // Unpin the left edge. Other pinned edges remain intact: layer.horizontalPins &= ~Pin.Min ``` The `~` operator used in the last example _negates_ the `Pin.Min` value, transforming it into _"everything BUT the Pin.Min"_ value. Bitwise operations are cool like that. > Note that even though you can pin any edge of any layer via the API, Pins will only have effect on layers that either: > > - belong to a `Frame`, or > - belong to a regular `Group` nested in some other container (i.e. not a page-level `Group`). ### Styling Containers Unlike Artboards, Frames and Graphics support the full range of styling options available to most Sketch layers: borders, fills, shadows, and even blur. Styling these new containers is no different than styling any other [`StyledLayer`](https://developer.sketch.com/reference/api/#style): ```js const { Style } = require('sketch/dom') let graphic = ... graphic.style.fills = [ { color: '#c0ffee', fillType: Style.FillType.Color, thickness: 5, }, ] graphic.style.borderOptions = { dashPattern: [20, 5, 20, 5], } ``` The legacy `Artboard.background` property, which remains intact for backwards compatibility, now acts as a mere proxy for the `fills` array: by adding or removing fills from the receiver container as necessary. ## Other Notable API Changes ### Added an option to specify the output filename in `export()` Now you may specify the destination filename directly via the `filename` option: ```js const targetDirectory = '/path/to/output/folder' const filename = 'an-arbitrary-file-name.png' sketch.export(layer, { output: targetDirectory, filename: filename, formats: ['png'], }) const exportedImagePath = [targetDirectory, filename].join('/') ``` ### Renamed `Style.blur` to `Style.blurs` This uniforms the `Style` API surface, as all other style parts are already plural: `fills`, `borders`, `shadows`. The new `Style.blurs` array will contain either one or zero `Blur` objects. ### Added `Shadow.isInnerShadow` Use this new property to distinguish an inner shadow from a drop shadow. ### Removed `MSGroup.children()` This internal Sketch method was used to collect all container's children layers recursively (i.e. including grandchildren, grand-grandchildren and so on). The following function will quickly list all children layers of the given container: ```js function childrenOf(container) { return sketch.find('*', container) } // Works on any layer container: childrenOf(frame) childrenOf(graphic) childrenOf(page) childrenOf(group) childrenOf(shapeGroup) ``` ### Removed `MSPage.currentArtboard()` This internal Sketch method was used to identify the artboard containing the currently selected layer (or, in case there are multiple selected layers, an artboard containing the first one). The following function will return _all_ top-level containers (Frames/Graphics/Artboards) that either: - have at least one children layer selected, or - are selected themselves ```js function topLevelContainersWithSelection(page) { return page.selectedLayers.reduce((prev, layer) => { // Include all explicitly selected top-level containers if (layer.type === sketch.Types.Artboard) { return prev.concat(layer); } // Otherwise try to reach this layer's top-level container if one exists // (i.e. this layer does not lay directly on the page) return prev.concat(layer.getParentArtboard() ?? []); }, []); } let page = // ... const currentArtboard = topLevelContainersWithSelection(page)[0]; ``` ### Removed `MSDocument.artboards()`, `MSDocument.hasArtboards()`, and similar methods Consider using the `find` function from the JavaScript API: ```js const { find, Document, Page } = require("sketch/dom"); const nativeMSDocumentInstance = // ... const document = Document.from(nativeMSDocumentInstance); const artboards = find("Artboard", document); // or, in case of MSPage: const nativeMSPageInstance = // ... const page = Page.from(nativeMSPageInstance); const artboards = find("Artboards", page); ``` ### Removed `MSPage.artboardWithID()`, and `MSPage.artboardNamed()` methods Consider using `find`: ```js const id = 'XX-ZZZ-YY' const artboardWithSaidID = find(`Artboard, #${id}`, page) // Note the # symbol before the identifier! const name = 'XYZ' const firstArtboardWithSaidName = find(`Artboard, [name=${name}]`, page) // As far as `name` goes, find() can also match: // a full string but ignoring case: find(`Artboard, [name~=${nAmE}]`) // a substring find(`Artboard, [name*=${substring}]`) // (and case-insensitive substring) find(`Artboard, [name~*=${sUbStTiNg}]`) // a prefix find(`Artboard, [name^=${prefix}]`) // a suffix find(`Artboard, [name$=${suffix}]`) ``` ### Deprecating `Document.ColorSpace.Unmanaged` In Sketch version 90 and earlier, the default color profile was set to _Unmanaged_, which meant we'd use your display's settings but export as _sRGB_. From version 91 onwards, we’ve removed the _Unmanaged_ option. All documents that previously used this profile now use _sRGB_ by default. [`Document.changeColorSpace()`](https://developer.sketch.com/reference/api/#change-the-color-space) and related APIs are still available for converting documents between _sRGB_ and _P3_ color profiles. ## Related resources - [What's New in Sketch Athens (2025.1)](https://www.sketch.com/changelog/athens/) - [Frames and Graphics: A not-so-short guide](https://www.sketch.com/blog/frames/) - [API Reference](/reference/api)