Skip to main content
dublove
Legend
Oct 10, 2026 4:20 AM UTC
Answered

Is it possible to preserve the image and the outermost frame, and remove any unnecessary frames?

Hello everyone.
This image has been inserted into the text and contains unnecessary frames.
Now I just want to keep the innermost image and outermost frame, delete the remaining extra frames, and keep the image in its original inserted state in the text.
Can it be implemented using scripts?

Thanks

 

    Manan JoshiCorrect answer
    Community Expert
    Oct 10, 2026 9:54 AM UTC

    Try the following code

    function findImage(container) {
    while(container.pageItems.length)
    container = container.pageItems[0]
    if(container)
    return container
    else
    return ""
    }

    var container, img
    for(var i = 0; i < app.documents[0].stories.length; i++) {
    var rectCol = app.documents[0].stories[i].rectangles
    for(var j = 0; j < rectCol.length; j++) {
    container = rectCol[j]
    img = findImage(container)
    if(img != ""){
    app.select(img)
    app.cut()
    app.select(container)
    app.pasteInto()
    }
    }
    }

    There are many assumptions made but you get the idea. Let me know if this is what you are after or not

    -Manan

    20 replies

    dublove
    dubloveAuthor
    Legend
    Oct 12, 2026 8:10 AM UTC

    Have I been banned again?

    Translate
    Community Expert
    Oct 12, 2026 12:41 PM UTC

    No you are not. I can see your post

    Translate
    -Manan
    dublove
    dubloveAuthor
    Legend
    Oct 10, 2026 12:44 PM UTC

    Hi ​@rob day 

    Also, I get different results depending on which container is selected

    Yes.

    ​@Manan Joshi 

    Also,Also, when using the selection tool, it seems that the outermost frame has been replaced?

    is there a way to automatically convert selected target to the outermost framework?

    Translate
    rob day
    Community Expert
    Community Expert
    Oct 11, 2026 4:37 PM UTC

    It might be easier to write a script that would copy only the image to the clipboard and delete its containers before you Paste Into. Then you wouldn’t get the nested containers that are causing so many problems. Something like this copies the image to the clipboard and removes its containers:

     

    //selected image or its container(s)
    var s = app.activeDocument.selection[0]

    //set the selection the nested image
    if (s.constructor.name != "Image") {
    while (app.selection[0].constructor.name != "Image") {
    app.select(app.selection[0].pageItems[0])
    }
    }

    //copy the image to the clipboard and remove the parent container
    app.copy()
    while (app.selection[0].parent.constructor.name != "Spread") {
    app.select(app.selection[0].parent)
    }
    app.selection[0].remove()

     

    Running the script with an image or container selected copies the image to the clipboard

     

     

    Select the empty rectangle and Paste Into

     

     

    Translate
    dublove
    dubloveAuthor
    Legend
    Oct 11, 2026 5:09 PM UTC

    I have now obtained the outermost and innermost objects.
    It doesn't matter whether the target is an image or not.
     

    function getInnerMost(mySel) {
     while (mySel.pageItems.length > 0) {
     mySel = mySel.pageItems[0]
     }
     return mySel;
    }

     

    Translate
    Community Expert
    Oct 10, 2026 10:30 AM UTC

    Try it on a test document first.

    Select the outermost frame first 
          


    #target "InDesign"

    (function () {
    if (app.documents.length === 0 ||
    app.selection.length !== 1) {
    alert("Select the outermost frame first.");
    return;
    }

    var outer = app.selection[0];
    var graphics;

    try {
    graphics = outer.allGraphics;

    if (graphics.length !== 1) {
    alert("Expected exactly one graphic.");
    return;
    }

    if (graphics[0].parent === outer) {
    alert("The graphic is already in the outer frame.");
    return;
    }
    } catch (e) {
    alert("Select a valid graphic container.");
    return;
    }

    try {
    app.doScript(function () {
    // Select the actual graphic, not its frame.
    app.select(outer.allGraphics[0]);
    app.cut();

    // Keep the original anchored container.
    app.select(outer);

    // Replace nested content with the graphic.
    app.pasteInto();

    }, ScriptLanguage.JAVASCRIPT,
    undefined,
    UndoModes.ENTIRE_SCRIPT,
    "Remove nested frames");

    } catch (e) {
    alert("Operation failed: " + e.message);
    }
    })();


    I’d be dubious about a global change in case it does something unpredictable. 
     

    You can run this across the entire InDesign document or a single text thread, without selecting each anchored image individually.

    1. Selected frame: Process one anchored object.
    2. Current text thread: Process all eligible anchored objects throughout the connected story, including overset text.
    3. Entire document: Process every eligible anchored object across all stories.

    This was trickier so forgive me if it doesn’t work the best but it works for me.

     

    #target "InDesign"

    (function () {
    if (app.documents.length === 0) {
    alert("Open an InDesign document first.");
    return;
    }

    var doc = app.activeDocument;
    var chosen = (app.selection.length === 1) ? app.selection[0] : null;
    var selectedStory = findStory(chosen);
    var scope = selectedStory ? "Selected text story" : "Entire document";
    var candidates = [], failures = [], skippedReasons = [];
    var seen = {}, nestedCount = 0, skipped = 0;
    var cleaned = 0, deletedFrames = 0, alreadyDirect = 0;

    function type(o) {
    try { return o.constructor.name; } catch (e) { return ""; }
    }
    function valid(o) {
    try { return !!(o && o.isValid); } catch (e) { return false; }
    }
    function frame(o) {
    var t = type(o);
    return t === "Rectangle" || t === "Oval" || t === "Polygon";
    }
    function locked(o) {
    try { return !!(o.locked || o.itemLayer.locked); }
    catch (e) { return true; }
    }
    function findStory(o) {
    for (var n = 0; n < 50 && o; n++) {
    try {
    if (type(o) === "Story") return o;
    if (o.parentStory && valid(o.parentStory)) return o.parentStory;
    if (o === doc) break;
    o = o.parent;
    } catch (e) { break; }
    }
    return null;
    }
    function sameObject(a, b) {
    try { return a.id === b.id; } catch (e) { return false; }
    }

    // Check both nested-item collections; never mistake a distant descendant
    // for a direct child. Duplicate items are deduplicated by their IDs.
    function childrenOf(parent) {
    var result = [], seenChildren = {};
    var lists = [];
    try { lists.push(parent.allPageItems); } catch (e) {}
    try { lists.push(parent.pageItems.everyItem().getElements()); }
    catch (e) {}
    for (var j = 0; j < lists.length; j++) {
    var arr = lists[j];
    for (var k = 0; arr && k < arr.length; k++) {
    var child = arr[k];
    try {
    if (valid(child) && sameObject(child.parent, parent) &&
    !seenChildren[child.id]) {
    result.push(child);
    seenChildren[child.id] = true;
    }
    } catch (e) {}
    }
    }
    return result;
    }

    function inspect(outer, report) {
    function reject(why) {
    if (report) report.reason = why;
    return null;
    }
    try {
    if (!frame(outer)) return reject("outer object is not a graphic frame");
    if (locked(outer)) return reject("outer frame or layer is locked");
    var all = outer.allGraphics;
    if (all.length !== 1) return reject("graphic count is " + all.length);
    var originalGraphic = all[0];
    var inner = originalGraphic.parent;
    if (!frame(inner)) return reject("graphic is inside " + type(inner));
    if (sameObject(inner, outer)) return reject("already direct");
    if (locked(inner)) return reject("image frame or layer is locked");

    // Graphic itself is allowed as the ONLY child of the image frame.
    var inside = childrenOf(inner);
    for (var i = 0; i < inside.length; i++) {
    if (!sameObject(inside[i], originalGraphic))
    return reject("inner frame also contains " + type(inside[i]));
    }

    var current = inner;
    var numberOfFrames = 0;
    for (var depth = 0; depth < 80; depth++) {
    var parent = current.parent;
    if (!frame(parent))
    return reject("chain passes through " + type(parent));
    if (locked(parent))
    return reject("nested frame or layer is locked");

    var direct = childrenOf(parent);
    if (direct.length !== 1 || !sameObject(direct[0], current))
    return reject("frame " + parent.id + " has " + direct.length +
    " direct page items, expected one");

    numberOfFrames++;
    if (sameObject(parent, outer)) {
    // Includes the innermost image frame itself. Thus a
    // one-frame nest is no longer missed (v3 skipped it).
    return {
    outer: outer,
    graphic: originalGraphic,
    branch: current,
    frames: numberOfFrames
    };
    }
    current = parent;
    }
    return reject("nesting exceeds 80 levels");
    } catch (e) {
    return reject(e.message);
    }
    }

    function addIfAnchored(o) {
    try {
    if (!valid(o) || type(o.parent) !== "Character" || !frame(o)) return;
    if (seen[o.id]) return;
    if (selectedStory) {
    var owningStory = o.parent.parentStory;
    if (!valid(owningStory) || !sameObject(owningStory, selectedStory))
    return;
    }
    seen[o.id] = true;
    if (o.allGraphics.length !== 1) return;
    if (sameObject(o.allGraphics[0].parent, o)) {
    alreadyDirect++;
    return;
    }
    nestedCount++;
    var report = {reason: "unknown"};
    var entry = inspect(o, report);
    if (entry) candidates.push(o);
    else {
    skipped++;
    skippedReasons.push("ID " + o.id + ": " + report.reason);
    }
    } catch (e) {
    skipped++;
    skippedReasons.push("ID " + (o ? o.id : "?") + ": " + e.message);
    }
    }

    // First enumerate all story objects (also catches overset/inline ones).
    var stories = selectedStory ? [selectedStory] :
    doc.stories.everyItem().getElements();
    for (var s = 0; s < stories.length; s++) {
    try {
    var storyItems = stories[s].pageItems.everyItem().getElements();
    for (var p = 0; p < storyItems.length; p++)
    addIfAnchored(storyItems[p]);
    } catch (e) {}
    }

    // Second pass: the document's allPageItems can include anchors not
    // exposed by the story.pageItems enumeration in a particular version.
    try {
    var docItems = doc.allPageItems;
    for (var a = 0; a < docItems.length; a++)
    addIfAnchored(docItems[a]);
    } catch (e) {}

    if (!candidates.length) {
    alert("Clean Nested Anchored Frames v4" +
    "\rScope: " + scope +
    "\rNested images: " + nestedCount +
    "\rEligible: 0" +
    "\rAlready direct: " + alreadyDirect +
    "\rSkipped: " + skipped +
    (skippedReasons.length ? "\r\r" + skippedReasons.slice(0, 6).join("\r") : "") +
    "\r\rNo changes made.");
    return;
    }

    if (!confirm("Clean nested anchored images?" +
    "\rScope: " + scope +
    "\rEligible: " + candidates.length +
    "\rSkipped: " + skipped +
    "\r\rKeep the original anchored outer frame and placed graphic; remove all inner frames."))
    return;

    function spreadTopLeft(o) {
    try {
    var r = o.resolve(AnchorPoint.TOP_LEFT_ANCHOR,
    CoordinateSpaces.SPREAD_COORDINATES);
    if (r && r[0] && r[0].length === 2)
    return [Number(r[0][0]), Number(r[0][1])];
    } catch (e) {}
    return null;
    }
    function boundsTopLeft(o) {
    try {
    var b = o.geometricBounds;
    return [Number(b[1]), Number(b[0])];
    } catch (e) { return null; }
    }

    function findDirectGraphic(outer, oldID) {
    var all = outer.allGraphics;
    for (var i = 0; i < all.length; i++) {
    try {
    if (all[i].id !== oldID && sameObject(all[i].parent, outer))
    return all[i];
    } catch (e) {}
    }
    return null;
    }

    function cleanOne(outer) {
    var entry = inspect(outer);
    if (!entry) return false;
    var graphic = entry.graphic;
    var oldID = graphic.id;
    var branch = entry.branch;
    var outerID = outer.id;
    var oldPos = spreadTopLeft(graphic);
    var oldBounds = boundsTopLeft(graphic);
    var pasted = null;

    app.select(graphic);
    app.copy();
    app.select(outer);

    // Try Paste Into while original image is still intact.
    try {
    app.pasteInto();
    pasted = findDirectGraphic(outer, oldID);
    } catch (e) {}

    if (!valid(pasted)) {
    // Only retry after verifying Paste Into did not add objects.
    if (!valid(branch) || outer.allGraphics.length !== 1 ||
    childrenOf(outer).length !== 1)
    throw Error("Paste Into altered the frame unexpectedly; undo the script.");
    branch.remove();
    app.select(outer);
    app.pasteInto();
    pasted = findDirectGraphic(outer, oldID);
    } else {
    if (valid(branch)) branch.remove();
    }

    // Prevent counting an operation as a success unless InDesign
    // really has just the image directly in the original outer frame.
    if (!valid(pasted) || !sameObject(pasted.parent, outer) ||
    outer.id !== outerID || outer.allGraphics.length !== 1)
    throw Error("Could not verify graphic in the original outer frame; undo the script.");

    // Paste Into may recenter graphics. Restore the graphic's location
    // without changing its size or refitting the image.
    var newPos = spreadTopLeft(pasted);
    var initial = oldPos && newPos ? oldPos : oldBounds;
    var current = oldPos && newPos ? newPos : boundsTopLeft(pasted);
    if (initial && current) {
    var dx = initial[0] - current[0];
    var dy = initial[1] - current[1];
    if (isFinite(dx) && isFinite(dy) &&
    (Math.abs(dx) > 0.01 || Math.abs(dy) > 0.01))
    pasted.move(undefined, [dx + " pt", dy + " pt"]);
    }
    deletedFrames += entry.frames;
    return true;
    }

    function doCleanup() {
    for (var i = 0; i < candidates.length; i++) {
    var outer = candidates[i];
    try {
    if (valid(outer) && cleanOne(outer)) cleaned++;
    } catch (e) {
    failures.push("Object " + outer.id + ": " + e.message);
    break;
    }
    }
    try { app.select(null); } catch (e) {}
    }

    try {
    app.doScript(doCleanup, ScriptLanguage.JAVASCRIPT, undefined,
    UndoModes.ENTIRE_SCRIPT, "Clean nested anchored frames v4");
    } catch (e) { failures.push(e.message); }

    var message = "Clean Nested Anchored Frames v4" +
    "\rScope: " + scope +
    "\rNested images found: " + nestedCount +
    "\rImages cleaned: " + cleaned +
    "\rFrames removed: " + deletedFrames +
    "\rAlready direct: " + alreadyDirect +
    "\rSkipped: " + skipped;
    if (skippedReasons.length)
    message += "\r\rSkipped details:\r" + skippedReasons.slice(0, 6).join("\r");
    if (failures.length)
    message += "\r\rStopped: " + failures.join("\r") +
    "\rUse one Undo to revert the operation.";
    alert(message);
    })();

     

    Translate
    dublove
    dubloveAuthor
    Legend
    Oct 10, 2026 10:43 AM UTC

    Hi Eugene Tyson.

    Thanks.
    But I didn't test this successfully.
    Manan Joshi seems to be what he wants.

    Translate
    Community Expert
    Oct 10, 2026 10:58 AM UTC

    i don’t understand. I used your document and both worked fine. 

    I tried ​@Manan Joshi script too, and if you run it twice it deletes the image completely. 

    So I have no idea what you expect here. But you’re welcome to the scripts. They worked for me on your sample file.

    Translate
    Manan JoshiCommunity ExpertCorrect answer
    Community Expert
    Oct 10, 2026 9:54 AM UTC

    Try the following code

    function findImage(container) {
    while(container.pageItems.length)
    container = container.pageItems[0]
    if(container)
    return container
    else
    return ""
    }

    var container, img
    for(var i = 0; i < app.documents[0].stories.length; i++) {
    var rectCol = app.documents[0].stories[i].rectangles
    for(var j = 0; j < rectCol.length; j++) {
    container = rectCol[j]
    img = findImage(container)
    if(img != ""){
    app.select(img)
    app.cut()
    app.select(container)
    app.pasteInto()
    }
    }
    }

    There are many assumptions made but you get the idea. Let me know if this is what you are after or not

    Translate
    -Manan
    dublove
    dubloveAuthor
    Legend
    Oct 10, 2026 10:43 AM UTC

    Hi ​Manan Joshi.

    This is okay, that's roughly what it means.
    Only applicable to the currently selected object.

    var d = app.activeDocument;
    var item = d.selection[0];
    var items = d.selection;
    function findImage(container) {
    while (container.pageItems.length)
    container = container.pageItems[0]
    if (container)
    return container
    else
    return ""
    }

    for (var j = 0; j < items.length; j++) {
    container = items[j]
    img = findImage(container)
    if (img != "") {
    app.select(img)
    app.cut()
    app.select(container)
    app.pasteInto()
    }
    }
    Translate
    rob day
    Community Expert
    Community Expert
    Oct 10, 2026 12:03 PM UTC

    I think you will also need to check the selection, I get an error if the image and not one of the containers is selected.

    Translate