Summary
When you remove a ComponentNode from a page, Figma silently also removes any orphaned instances of that component on the same page. If you spread page.children into an array first and then loop over it to remove nodes, the cascade removal makes some references stale, causing subsequent .remove() calls to throw:
in remove: The node with id "X:Y" does not exist
Reproduction
// Page contains: compFrame (holds ComponentSet), plus some orphaned instances
// of those components left over from a previous failed build.
await page.loadAsync();
const children = [...page.children]; // snapshot includes both compFrame + orphaned instances
for (const child of children) {
child.remove();
// Removing compFrame removes the ComponentSet and its variants.
// Figma also silently removes the orphaned instances of those variants.
// Later iterations try to remove those instances — they no longer exist → throws.
}
Expected behavior
Either:
- Removing a component should not cascade-remove instances (instances should become detached/broken, as they do in the UI), or
- The cascade behavior should be documented, so developers know not to snapshot
page.children before a remove loop.
Workaround
Drain page.children live instead of snapshotting:
while (page.children.length > 0) page.children[0].remove();
This always reads the fresh live list, so cascade removals are naturally absorbed.
Context
Hit this when building a plugin that clears and regenerates pages on each run (getOrResetPage). A previous build had failed mid-way and left orphaned button instances on the page. On the next run, clearing the page triggered the cascade.
Summary
When you remove a
ComponentNodefrom a page, Figma silently also removes any orphaned instances of that component on the same page. If you spreadpage.childreninto an array first and then loop over it to remove nodes, the cascade removal makes some references stale, causing subsequent.remove()calls to throw:Reproduction
Expected behavior
Either:
page.childrenbefore a remove loop.Workaround
Drain
page.childrenlive instead of snapshotting:This always reads the fresh live list, so cascade removals are naturally absorbed.
Context
Hit this when building a plugin that clears and regenerates pages on each run (
getOrResetPage). A previous build had failed mid-way and left orphaned button instances on the page. On the next run, clearing the page triggered the cascade.