Plugin issue in Athens

My plugin runs with the below error with 「Athens」2025.1 version:
artboard.sketchObject.children is not a function. (In ‘artboard.sketchObject.children()’, ‘artboard.sketchObject.children’ is undefined);

When my plugin executed the below code:

originArtboards.forEach(artboard => {
const cnt = artboard.sketchObject.children().count()
})

originArtboards.forEach(artboard => {
const children = artboard.sketchObject.children();
const layerEnumerator = children.objectEnumerator();
const layersCount = children.count();
})

The above code could run well in the previous all versions, so how to rewrite the code to compatible with the 「Athens」version

1 Like

Hey there,

While it’s not clear how children and layerEnumerator are used later in the script, one way to get rid of them is to use sketch.find JS API:

const sketch = require('sketch')

// ...

originArtboards.forEach(artboard => {
    // These children are normal JS objects,
    // so you can forEach/filter/map/etc them directly
    const children = sketch.find("*", artboard);
    const chldrenCount = children.length;
});

I noticed that the official plugin development API documentation lacks comprehensiveness. For example, APIs such as artboard.sketchObject.children or artboard.sketchObject.children().objectEnumerator() used in my plugin code are not explained in the official documentation. Could you please provide me with the latest and most detailed plugin development API documentation? Thank you very much.

So as soon as you see sketchObject in your code it means that everything that follows (children() and objectEnumerator() calls in this case) is a private API.

These private methods and functions are not documented by design – since they exist for Sketch itself to use, not plugin developers. Their usage in plugins, while sometimes justified and unavoidable even, usually predates the official JavaScript API – so there’s a good chance you may replace them with stable and more ergonomic modern counterparts.

For instance, objectEnumerator() is nothing more than an awkward way to iterate the children array – which is not needed when working with normal JS API arrays and objects.

And the undocumented children() call itself could be replaced by sketch.find() one I’ve mentioned above that does exactly the same.


I also agree that documentation should probably cover not only what’s available in the JS API, but also help with migration from outdated private APIs to modern public ones. Until it does, I’m here to answer any of your further questions!

My code:

context.page = context.document.selectedPage;
context.artboard = sketch.Artboard.fromNative(context.page.sketchObject.currentArtboard());

Code error:

context.page.sketchObject.currentArtboard is not a function。

How to find a replacement method?

I’m not familiar with the currentArtboard() method – does it return the first artboard that contains any of the currently selected layers?

I think you may want to do it like this:

let page = sketch.getSelectedDocument().selectedPage
let selection = page.selectedLayers

let artboardsWithSelection = selection.reduce((prev, layer) => {
  if (layer.type == sketch.Types.Artboard) {
    return prev.concat(layer);
  }
  return prev.concat(layer.getParentArtboard() ?? [])
}, [])

API Docs:

thank ! I have other questions These methods errors

let hasFixedLeft = layer?.sketchObject?.hasFixedLeft() || false;
let hasFixedRight = layer?.sketchObject?.hasFixedRight() || false;
let hasFixedTop = layer?.sketchObject?.hasFixedTop() || false;
let hasFixedBottom = layer?.sketchObject?.hasFixedBottom() || false;
let hasFixedWidth = layer?.sketchObject?.hasFixedWidth() || false;
let hasFixedHeight = layer?.sketchObject?.hasFixedHeight() || false;
let canFixLeft = layer?.sketchObject?.canFixLeft() || false;
let canFixRight = layer?.sketchObject?.canFixRight() || false;
let canFixTop = layer?.sketchObject?.canFixTop() || false;
let canFixBottom = layer?.sketchObject?.canFixBottom() || false;
let canFixWidth = layer?.sketchObject?.canFixWidth() || false;
let canFixHeight = layer?.sketchObject?.canFixHeight() || false;
export function exportImage(layer: Layer, format: SMExportFormat, path: string, name: string) {
    let document = context.sketchObject.document;
    let slice = MSExportRequest.exportRequestsFromExportableLayer(layer.sketchObject).firstObject();
    let savePath = [
        path,
        "/",
        format.prefix,
        name,
        format.suffix,
        ".",
        format.format
    ].join("");

    slice.scale = format.scale;
    slice.format = format.format;


    document.saveArtboardOrSlice_toFile(slice, savePath);
    return savePath;
}
  1. As for hasFixed*() methods, there’re JS APIs available to inspect layer’s sizing and pinning details:

    const { FlexSizing } = require("sketch/dom");
    
    let hasFixedWidth = (layer.horizontalSizing === FlexSizing.Fixed);
    let hasFixedHeight = (layer.verticalSizing === FlexSizing.Fixed);
    

    Now determining whether a certain layer side is pinned is a bit more involved (pun intended, because this involves bitwise operators):

    const { Pin } = require("sketch/dom");
    
    // For the horizontal axis
    let hasFixedLeft  = (layer.horizontalPins & Pin.Min);
    let hasFixedRight = (layer.horizontalPins & Pin.Max);
    // And for the vertical axis
    let hasFixedTop    = (layer.verticalPins & Pin.Min);
    let hasFixedBottom = (layer.verticalPins & Pin.Max);
    

    FYI: Pins are only active on layers that either

    • a) belong to a Frame, or
    • b) don’t belong to a Frame nor Graphic, but to a regular non-top-level Group (ie it’s not a page-level one).

    You can check the type of a specific Group object via its groupBehavior property:

    const { GroupBehavior } = require('sketch/dom');
    
    if (layer.parent?.groupBehavior === GroupBehavior.Graphic) {
        // The parent is a <Graphic> group, which does NOT allow layer pinning.
        // In order words, layerHasFixedEdge() will always return `false` for any sublayer of this group
        const pinsAllowed = false;
    }
    
  2. All of the canFix*() checks are redundant in Athens as they’d always evaluate to true (unless you’re in a Graphic group, in which case the result will be false – again, see above).

  3. I mean your exportImage() function might as well be rewritten to use the JS API:

    function exportImage(layer, format, path, name) {
      let filename = [
        format.prefix, name, format.suffix,
        ".", format.format
      ].join("");
    
      sketch.export(layer, {
        output: path,
        formats: [format.format],
        filename: filename,
        scales: [format.scale]
      });
    
      return [path, filename].join("/")
    }
    

Thanks a lot! Some APIs aren’t in the official docs, and others are hard to find. The missing info makes development tricky. Could you improve the docs with more details and examples? It’d help a ton!

2 Likes

How can I get layout information for a Frame? I’d like to display the same type of data as seen in Sketch, such as horizontal/vertical alignment, spacing, and margins; and how can I get layer blur data (e.g., saturation values of background blur effects)?

Do you mean the Stack Layout properties? There’s no JS API for Stacks in 2025.1, but there is something in 2025.2 Beta:

console.log(frame.stackLayout)
/*
{
    alignItems = 2;
    direction = 0;
    gap = 0;
    id = 18617;
    justifyContent = 0;
    padding =     {
        bottom = 0;
        left = 14;
        right = 89;
        top = 44;
    };
    type = StackLayout;
}
*/

Drop me a DM if you’re interested in figuring out the details :technologist:


As for blurs, saturation is not currently exposed in the JS API either, so you have to go via sketchObject here:

const layer = // ...
const saturation = Number(layer.style.blurs[0].sketchObject.saturation())

All other blur properties should be available in layer.style.blurs[0].

export function is(layer, theClass) {
  if (!layer) return false;
  const _class = layer.class();
  return _class === theClass;
}

export function isExportable(layer) {
  return is(layer, MSTextLayer) ||
    is(layer, MSShapeGroup) ||
    is(layer, MSRectangleShape) ||
    is(layer, MSOvalShape) ||
    is(layer, MSShapePathLayer) ||
    is(layer, MSTriangleShape) ||
    is(layer, MSStarShape) ||
    is(layer, MSPolygonShape) ||
    is(layer, MSBitmapLayer) ||
    is(layer, MSSliceLayer) ||
    is(layer, MSSymbolInstance) ||
    is(layer, MSLayerGroup) ||
    isSliceGroup(layer)
}

Hello, the above method in plugin will throw an error in Sketch 2025: ReferenceError: Can’t find variable: MSArtboardGroup. How should these two utility functions be rewritten?

And, how can we replace the use of the method layer.shouldBreakMaskChain() with the same effect?

Hey there,

Hmm I’m not seeing any mentions of MSArtboardGroup in the code you’ve shared. But anyway, since Sketch 2025.1 this class no longer exists, so if you want to check if some layer is in fact a top-level Frame (aka an Artboard in older terms):

  • check that it’s a group (Group/MSLayerGroup)
  • check its groupBehavior to be a Frame/Graphic – see here
  • check that its parent is a Page/MSPage

I believe this function is still the only way to inspect this particular property, so it should work fine as is?

Thanks!I’ve solved the above issues, but I have some other questions as below:

sktech.Types defined as:
 { 
  Group: 'Group',
  Page: 'Page',
  Artboard: 'Artboard',
  Shape: 'Shape',
  Style: 'Style',
  Blur: 'Blur',
  Border: 'Border',
  BorderOptions: 'BorderOptions',
  Fill: 'Fill',
  Gradient: 'Gradient',
  GradientStop: 'GradientStop',
  Shadow: 'Shadow',
  Image: 'Image',
  Text: 'Text',
  Document: 'Document',
  Library: 'Library',
  SymbolMaster: 'SymbolMaster',
  SymbolInstance: 'SymbolInstance',
  Override: 'Override',
  ImageData: 'ImageData',
  Flow: 'Flow',
  HotSpot: 'HotSpot',
  ImportableObject: 'ImportableObject',
  SharedStyle: 'SharedStyle',
  DataOverride: 'DataOverride',
  ShapePath: 'ShapePath',
  Slice: 'Slice',
  ExportFormat: 'ExportFormat',
  CurvePoint: 'CurvePoint',
  ColorAsset: 'ColorAsset',
  GradientAsset: 'GradientAsset',
  Swatch: 'Swatch' 
}

But my plugin code used some internal private Class such as MSLayerGroup MSShapeGroup MSBitmapLayer MSShapePathLayer MSOvalShape MSTriangleShape ,I’m not quite sure how the types of layers created by these Classes correspond one-to-one with the types defined above. Please shed some light on this for me.

And,how to use the official standard api, how to get the radius of a layer node with MSRectangleShape type, please show me some examples

Oh there’s a handy function available to quickly convert a native class into a JS one: sketch.fromNative():

const sketch = require('sketch')

let anMSBitmapLayer = // ...
let jsImage = sketch.fromNative(anMSBitmapLayer) 

console.log(jsImage.type)
// 👉 Image

I’m afaird there’s currently no way to read corner radius of a shape via the JS API (but it’s coming in 2025.2 :raising_hands:). You’ll have to drop down to the native API for that:

const shape = ...
const internalCorners = shape.style.sketchObject.corners()

// does it have any rounded corners?
if (internalCorners) {
  // Rounded: 0
  // Smooth: 1
  // Angled: 2
  // InsideSquare: 3
  // InsideArc: 4
  const style = internalCorners.style()
  if (style === 1) {
    // Smooth corners, etc
  }


  // note: the `radii()` must be converted to a proper JS array
  const { toArray } = require('util')
  const radii = toArray(internalCorners.radii()).map((r) => {
    if (r.doubleValue) {
      return Number(r.doubleValue())
    }
    return Number(r)
  })

  console.log(radii[0], radii[1]) // etc
}