@dunazee
New Linked Smart Object via Copy (v1.2) duplicates the original linked smart object into a new layer that points to a new file on disk, a copy of the original. The two linked layer objects can then be edited independently.
/*
New Linked Smart Object via Copy.jsx
Stephen Marsh
https://community.adobe.com/t5/photoshop-ecosystem-discussions/why-is-new-smart-object-via-copy-greyed-out-for-linked-smart-objects/td-p/14371680
v1.0 - 25th January 2024:
Initial release.
v1.1 - 27th January 2024:
Added filename validation so as not to overwrite an existing file.
v1.2 - 19th September 2026:
If the replacement file already exists, the user is prompted for a new name
(validated, re-prompted until valid) instead of the script cancelling.
Cancelling the prompt exits cleanly without changes.
*/
#target photoshop
// Smart object action manager properties
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
// Conditional check for the active layer being a linked smart object
if (so.getBoolean(stringIDToTypeID("linked"))) {
// Single history stage undo
activeDocument.suspendHistory("New Linked Smart Object via Copy.jsx", "main()");
}
function main() {
// Doc path, name and extension variables
var thePath = so.getPath(stringIDToTypeID("link")).fullName.replace(/(^.+\/)(.*)/, '$1');
var theName = so.getString(stringIDToTypeID("fileReference")).replace(/\.[^\.]+$/, '');
var theExt = so.getString(stringIDToTypeID("fileReference")).replace(/(.+)(\.[^\.]+$)/, '$2');
// Default copy filename
var copyName = theName + " copy";
// Check for filename clashes and allow the user to enter a new filename
var theCopy = new File(thePath + copyName + theExt);
if (theCopy.exists) {
var validName = false;
while (!validName) {
var newName = prompt(
'The file "' + copyName + theExt + '" already exists.\n\n' +
'Enter a new filename:',
copyName
);
// Cancel
if (newName === null) {
return;
}
// Remove leading/trailing spaces
newName = newName.replace(/^\s+|\s+$/g, '');
// Validate filename
if (newName === '') {
alert('Filename cannot be blank.');
continue;
}
// Remove extension if the user entered the original extension
var extRegex = new RegExp('\\' + theExt + '$', 'i');
if (extRegex.test(newName)) {
newName = newName.replace(extRegex, '');
}
// Check for invalid Windows filename characters
if (/[\\\/:\*\?"<>\|]/.test(newName)) {
alert(
'The filename contains invalid characters.\n\n' +
'The following characters are not allowed:\n' +
'\\ / : * ? " < > |'
);
continue;
}
// Prevent the new filename from matching the original
if (newName.toLowerCase() === theName.toLowerCase()) {
alert(
'The new filename cannot be the same as the original filename.'
);
continue;
}
// Check whether the new filename already exists
theCopy = new File(thePath + newName + theExt);
if (theCopy.exists) {
alert(
'The file "' + newName + theExt + '" already exists.\n\n' +
'Please enter a different filename.'
);
copyName = newName;
continue;
}
// Filename is valid and available
copyName = newName;
validName = true;
}
}
// Final copy file
theCopy = new File(thePath + copyName + theExt);
// Duplicate the linked file
if (!new File(thePath + theName + theExt).copy(theCopy.fsName)) {
alert(
'Unable to create the copied file:\n\n' +
theCopy.fsName
);
return;
}
// Set the references to the original doc name and original layer ID
var origDocName = activeDocument.name;
var origLayerID = activeDocument.activeLayer.id;
// Attempt to unlock the layer before it is duplicated to the temp doc
try {
unlockActiveLayer();
} catch (e) {}
// Dupe the smart object layer to a new temp doc
var descriptor = new ActionDescriptor();
var reference = new ActionReference();
var reference2 = new ActionReference();
reference.putClass(s2t("document"));
descriptor.putReference(s2t("null"), reference);
descriptor.putString(s2t("name"), "tempDoc");
reference2.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
descriptor.putReference(s2t("using"), reference2);
descriptor.putString(s2t("layerName"), activeDocument.activeLayer.name);
executeAction(s2t("make"), descriptor, DialogModes.NO);
// Relink to the duplicated file copy
var idplacedLayerRelinkToFile = stringIDToTypeID("placedLayerRelinkToFile");
var desc15 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
desc15.putPath(idnull, theCopy);
executeAction(idplacedLayerRelinkToFile, desc15, DialogModes.NO);
// Dupe the relinked temp doc layer back to the original doc
var descriptor2 = new ActionDescriptor();
var reference3 = new ActionReference();
var reference4 = new ActionReference();
reference3.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
descriptor2.putReference(s2t("null"), reference3);
reference4.putName(s2t("document"), origDocName);
descriptor2.putReference(s2t("to"), reference4);
descriptor2.putString(s2t("name"), activeDocument.activeLayer.name);
executeAction(s2t("duplicate"), descriptor2, DialogModes.NO);
// Close the temp doc (still the active doc), which returns focus to the original doc
activeDocument.close(SaveOptions.DONOTSAVECHANGES);
// The new relinked layer is now the active layer in the original doc, store its ID
var newLayerID = activeDocument.activeLayer.id;
// Select the original (previous) layer in the stack
try {
selectLayerByID(origLayerID);
} catch (e) {
alert('Unable to select the original layer:\n\n' + e.message);
return;
}
/*
// Delete the now redundant original layer (already unlocked above)
try {
app.activeDocument.activeLayer.remove();
} catch (e) {
alert(
'Unable to delete the original layer. It may still be locked.\n\n' +
e.message
);
}
*/
// Reselect the new relinked layer
try {
selectLayerByID(newLayerID);
} catch (e) {
// Selection is left as-is
}
}
///// Functions /////
function s2t(s) {
return app.stringIDToTypeID(s);
}
function selectLayerByID(id) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putIdentifier(s2t("layer"), id);
desc.putReference(s2t("null"), ref);
desc.putBoolean(s2t("makeVisible"), false);
executeAction(s2t("select"), desc, DialogModes.NO);
}
function unlockActiveLayer() {
var lyr = activeDocument.activeLayer;
var locked = false;
try {
if (lyr.allLocked || lyr.pixelsLocked || lyr.positionLocked || lyr.transparentPixelsLocked) {
locked = true;
}
} catch (e) {
// Lock state could not be read, attempt the unlock anyway
locked = true;
}
if (!locked) {
return;
}
try {
lyr.allLocked = false;
} catch (e) {}
try {
lyr.pixelsLocked = false;
} catch (e) {}
try {
lyr.positionLocked = false;
} catch (e) {}
try {
lyr.transparentPixelsLocked = false;
} catch (e) {}
}
function deleteActiveLayer() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
desc.putReference(s2t("null"), ref);
executeAction(s2t("delete"), desc, DialogModes.NO);
}
- Copy the code text to the clipboard
- Open a new blank file in a plain-text editor (not in a word processor)
- Paste the code in
- Save as a plain text format file – .txt
- Rename the saved file extension from .txt to .jsx
- Install or browse to the .jsx file to run (see below)
EDIT:
Relink to New Linked Smart Object via Copy.jsx is a related script, which links to a different existing file, rather than creating a new copy of the current linked file. This is only mentioned as it's related, the previous New Linked Smart Object via Copy (v1.2) is the one that you are looking for. EDIT: It might work for you if you duplicated the active layer first.
/*
Relink to New Linked Smart Object via Copy.jsx
v1.0 - 9th September 2024, Stephen Marsh
NOTE: The script will relink to an existing file
https://community.adobe.com/t5/photoshop-ecosystem-discussions/please-fix-how-relink-to-file-and-replace-content-work/td-p/14846535
Related to:
https://community.adobe.com/t5/photoshop-ecosystem-discussions/why-is-new-smart-object-via-copy-greyed-out-for-linked-smart-objects/td-p/14371680
*/
#target photoshop
try {
// Smart object action manager properties
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("smartObject"));
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var so = executeActionGet(ref).getObjectValue(stringIDToTypeID("smartObject"));
// Conditional check for the active layer being a linked smart object
if (so.getBoolean(stringIDToTypeID("linked"))) {
// Single history stage undo
activeDocument.suspendHistory("Relink to New Linked Smart Object via Copy.jsx", "main()");
}
} catch (err) {
alert("Error!" + "\r" + err + "\r" + 'Line: ' + err.line);
}
function main() {
try {
// Convert the active linked smart object layer to an embedded smart object
executeAction(stringIDToTypeID("newPlacedLayer"), undefined, DialogModes.NO);
// Edit the smart object contents
executeAction(stringIDToTypeID("placedLayerEditContents"), new ActionDescriptor(), DialogModes.NO);
// Relink to file
var idselect = stringIDToTypeID("select");
var desc350 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref59 = new ActionReference();
var idmenuItemClass = stringIDToTypeID("menuItemClass");
var idmenuItemType = stringIDToTypeID("menuItemType");
var idplacedLayerRelinkToFile = stringIDToTypeID("placedLayerRelinkToFile");
ref59.putEnumerated(idmenuItemClass, idmenuItemType, idplacedLayerRelinkToFile);
desc350.putReference(idnull, ref59);
executeAction(idselect, desc350, DialogModes.ALL);
// Get the layer name
var layerName = app.activeDocument.activeLayer.name;
// Close and save
app.activeDocument.close(SaveOptions.SAVECHANGES);
// Convert the embedded smart object back to a linked smart object
executeAction(stringIDToTypeID("placedLayerConvertToLayers"), undefined, DialogModes.NO);
// Set the layer name
app.activeDocument.activeLayer.name = layerName;
} catch (err) {
alert("Error!" + "\r" + err + "\r" + 'Line: ' + err.line);
}
}