Photoshop batchPlay write XMPMetadata works for document but not layer

I will be grateful for any suggestions or corrections!

What works: using batchPlay, I can successfully read, update, and then write XMPMetadata for the current document, and subsequently read it back and my updates are present.

After reviewing posts by Simon Henke and Rob, I would anticipate that I could make a similar scenario work for layer metadata.

The scenario that does not work (code and results below):

  • I can read the metadata (using batchPlay) for the current layer and create a XMPMeta object. The serialized result appears to be valid, with an empty <rdf:Description/> element positioned in the DOM at the same location as it appears in the corresponding serialized document XMPMeta.
  • I can append an array item. The serialized XMPMeta object now includes the appended array item.
  • I can write the metadata (using batchPlay). The batchPlay return value is an array of one element, which is an empty object [{}]. There is no indication of anything other than success.
  • If I then re-read the metadata and create a new XMPMeta object(same code as before), the serialized result is the same as before, that is, lacking the appended array item.

The code I am using:

Code to read the layer metadata and inspect it:

const { action, app, core } = require("photoshop");
const { XMPMeta, XMPConst } = require("uxp").xmp;

function getLayerXMP()  {
    return action.batchPlay(
        [
            {
                _obj: "get",
                _target: {
                    _ref: [
                        { _property: "XMPMetadataAsUTF8" },
                        { _ref: "layer", _enum: "ordinal", _value: "targetEnum" },
                    ],
                },
            },
        ],
        { synchronousExecution: true }
    );
};

let xmpData = getLayerXMP();
let meta = new XMPMeta(xmpData);
let serialized = meta.serialize();

The resulting serialized value (omitting numerous blank lines after the last closing tag)


<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 10.0-c000 79.4332aae, 2026/08/17-13:39:43        ">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""/>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>

Code to append an array item and inspect the updated metadata:

meta.appendArrayItem(XMPConst.NS_DC, "description", "Set description Here", 0, XMPConst.ARRAY_IS_ALTERNATIVE);
let serialized = meta.serialize();

The resulting serialized value (again, omitting numerous blank lines)

<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 10.0-c000 79.4332aae, 2026/08/17-13:39:43        ">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
        xmlns:dc="http://purl.org/dc/elements/1.1/">
      <dc:description>
        <rdf:Alt>
          <rdf:li>Set description Here</rdf:li>
        </rdf:Alt>
      </dc:description>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>

The code to write the metadata:

async function setLayerXMP(xmpString) {
    try {
        return await core.executeAsModal(
            async () => {
                return await action.batchPlay(
                    [
                        {
                            _obj: "set",
                            _target: [
                                { _property: "XMPMetadataAsUTF8" },
                                { _ref: "layer", _enum: "ordinal", _value: "targetEnum" },
                            ],
                            to: {
                                _obj: "XMPMetadataAsUTF8",
                                XMPMetadataAsUTF8: xmpString,
                            },
                        },
                    ],
                    { synchronousExecution: false });
            }, { commandName: "Setting XMP..." });
    } catch (error) {
        console.error(error);
    }
};
let result = await setLayerXMP(meta.serialize());

The (stringified) return value:

[{}]

If anyone can make any suggestions, or tell me where I’m hgoing wrong, please do!
Thanks

Seems you don’t call it?

I’m sorry … that was a cut-and-paste error while assembling the text for this post. I do in fact call the correct “setLayerXMP” function …

I’ve corrected the original post …

It’s the read, not the write.

new XMPMeta(xmpData) gets the batchPlay result instead of the string inside it, so it parses nothing and gives you back an empty object. That’s your empty <rdf:Description>. Your re-read does the same thing, so it can’t show the appended item even when the write worked.

const r = await action.batchPlay([...], { synchronousExecution: true });
const meta = new XMPMeta(r[0].XMPMetadataAsUTF8);

Easy way to check: log r[0].XMPMetadataAsUTF8.length after your read.

Also, [{}] is what a successful set returns. A refusal comes back as an error descriptor instead, so [{}] wasn’t telling you anything was wrong.

Well, duh! Of course! It’s obvious once it’s pointed out. Thanks.