[UXP/batchPlay] How to recreate a Live Shape (arrow) on a new layer?

[UXP/batchPlay] How to reproduce a Live Shape (arrow, keyOriginType:9) on a new layer?

Hi everyone,

I’m developing a UXP plugin that saves layer groups as templates and reapplies them to other documents. I’ve hit a wall trying to programmatically reproduce arrow shapes (and other non-rectangle/ellipse Live Shapes).

What I have

I can successfully read the arrow layer’s data via batchPlay GET:

  • keyOriginType: 9 (arrow)
  • vectorMask.pathComponents with valid bézier data (percentUnit coordinates)
  • All subpath points, anchors, and control handles

The problem

I cannot recreate the arrow shape on a new layer. I’ve tried 14 different approaches over several weeks. Here’s a summary:

Approach A – make contentLayer with path data

// Tried with pathComponents, pathContents (_obj:"pathContents"), 
// and pathContents (_obj:"pathClass")
await batchPlay([{
  _obj: "make",
  _target: [{ _ref: "contentLayer" }],
  using: {
    _obj: "contentLayer",
    type: { _obj: "solidColorLayer", color: { ... } },
    shape: { _obj: "path", pathComponents: [ /* arrow data */ ] }
  }
}], {});

Result: No layer created (silent failure, no error thrown).

Approach B – Create shape layer → SET vectorMask

// 1. Create a rectangle shape layer
// 2. Set vectorMask with arrow path data
// 3. Clear keyOriginType with keyOriginType: []
await batchPlay([{
  _obj: "set",
  _target: [{ _ref: "path", _property: "vectorMask" }],
  to: { _obj: "pathClass", pathComponents: [ /* arrow data */ ] }
}], {});

Result: Layer stays as rectangle. Even though a subsequent GET confirms the arrow path data is stored in vectorMask, PS continues to render the rectangle from keyOriginType:1. Clearing keyOriginType: [] has no visual effect.

Approach C – Fill layer → vectorMask

// Tried: addMask vectorMaskRevealAll → set vectorMask,
//        set layer vectorMask directly,
//        workPath → select → addMask vectorMaskFromCurrentPath

Result: Always renders as full-canvas fill. The vectorMask SET completes without error but never visually applies.

What works (but has limitations)

duplicate is the only method that successfully preserves the arrow shape:

await batchPlay([{
  _obj: "duplicate",
  _target: [{ _ref: "layer", _enum: "ordinal", _value: "targetEnum" }],
  version: 5,
  to: { _ref: "document", _id: destDocId }
}], {});

This works, but requires keeping the original layer alive somewhere as a “master copy,” which adds complexity to the template system.

My questions

  1. Is there a correct batchPlay descriptor to create an arrow (or other non-rect/ellipse Live Shape) from scratch? If anyone has captured the descriptor via Alchemist or Actions panel → “Copy as JavaScript” when manually drawing an arrow, I’d love to see the structure.

  2. Is keyOriginType fundamentally read-only for creation purposes? My testing suggests PS only establishes it during the internal shape-creation flow and ignores external SET attempts.

  3. Is duplicate truly the only reliable path? If so, any tips on controlling placement (parent group, position) after cross-document duplicate?

Any insights from the UXP/batchPlay community would be hugely appreciated. Thanks!

Environment: Photoshop 2025, UXP 8.x, macOS