Copy/Paste Functionality for a Visual Workflow Builder

TL;DR: A naive copy-paste fails silently in complex visual workflow builders. This post covers how ByteChef solved recursive task renaming, ghost-paste bugs, and deep cloning challenges to ship a Copy/Paste system that handles even the most deeply nested workflow graphs.
Building a right-click menu is easy, but when you try building something more complex like a Copy/Paste system for a visual builder - that’s where the real engineering begins. The challenge at ByteChef wasn't just moving data. It was maintaining the integrity of a Directed Acyclic Graph (DAG) while making the experience feel completely effortless to the user. When the canvas involves complex logic trees, loops, and parallel workers, a naive copy-paste doesn't just fail. It creates "execution ghosts" that can crash a production engine.
Below is the solution to the complexity in pasteNode logic.
Recursive Task Renaming
In a workflow engine, the name of a task is its unique identifier. Copy a Condition node that contains five nested tasks and paste it, suddenly six tasks share identical IDs. The engine no longer knows which HTTP_Request_1 to execute, and in a production system, that can be catastrophic.
The solution was a recursive renameNestedTasks strategy. Renaming the parent node alone is insufficient. The logic must descend into every possible branch: caseTrue, iteratee, branches, and on-error-branch.
function renameNestedTasks(
task: WorkflowTask,
reservedNames: Set<string>
): WorkflowTask {
const newName = getUniqueName(task.name, reservedNames);
reservedNames.add(newName);
const renamedTask = {...task, name: newName};
if (renamedTask.parameters?.caseTrue) {
renamedTask.parameters.caseTrue = renamedTask.parameters.caseTrue.map(
(t: WorkflowTask) => renameNestedTasks(t, reservedNames)
);
}
if (renamedTask.parameters?.branches) {
renamedTask.parameters.branches = renamedTask.parameters.branches.map(
(branch: WorkflowBranch) => ({
...branch,
tasks: branch.tasks.map((t: WorkflowTask) =>
renameNestedTasks(t, reservedNames)
),
})
);
}
return renamedTask;
}By passing a reservedNames Set through the recursion, the system guarantees that every sub-task receives a unique identity before it ever touches the database. This eliminates the "collision" class of bugs, where a nested task in a copied branch accidentally references data belonging to the original.
Labeling
There is a distinct lack of polish when a builder generates labels like Send_Email_Copy_Copy_Copy. To address this, a getNextAvailableName utility was built using a targeted regular expression.
const labelWithCounterRegex = new RegExp(
`^${escapedCopiedLabel} \\((\\d+)\\)$`
);By scanning the existing workspace labels, the logic identifies the highest current increment. If "Process Data (2)" already exists, the system knows the next label must be "Process Data (3)". It turns a purely technical requirement (uniqueness) into a readable UI experience that respects the user's mental model of their workflow while keeping a clean and organized building interface.

The Deceptively Simple Problem: Copying Nested Nodes
When a user copies a node, especially a Task Dispatcher like a loop, branch, or parallel block - the system can't just copy an ID. It has to handle a full tree of nested data. Below is the core pasteNode logic that ties every concern together:
async function pasteNode(
copiedNode: WorkflowNode,
targetWorkflow: Workflow,
insertIndex: number
): Promise<WorkflowTask> {
const reservedNames = new Set(targetWorkflow.tasks.map((t) => t.name));
// Deep-clone to avoid mutating clipboard state
const clonedParameters = structuredClone(copiedNode.parameters ?? {});
// Strip positional metadata so the canvas re-calculates layout
if (copiedNode.metadata?.ui) {
copiedNode.metadata.ui = {
...copiedNode.metadata.ui,
nodePosition: undefined,
};
}
// Recursively assign unique names to every node in the subtree
const renamedTask = renameNestedTasks(
{...copiedNode, parameters: clonedParameters},
reservedNames
);
// Derive readable label increment
const existingLabels = targetWorkflow.tasks.map((t) => t.label ?? t.name);
renamedTask.label = getNextAvailableName(
copiedNode.label ?? copiedNode.name,
existingLabels
);
targetWorkflow.tasks.splice(insertIndex, 0, renamedTask);
return renamedTask;
}Each responsibility, deep cloning, collision prevention, label generation, and metadata stripping, feeds into one orchestrated operation.
The "Overlapping Node" Problem
To ensure the workflow doesn't look broken or cluttered after a paste, the system detects whether the workflow uses fixed positioning and triggers an automatic setResetWorkflowLayout(true). This forces the canvas to recalculate the entire tree, finding a natural position for the newly inserted node rather than hiding it underneath its origin.
Technical Snapshotting vs. Shallow Copying
Early iterations struggled with Task Dispatchers (Branches, Loops, Parallel blocks) not pasting correctly when nested deeply within other Dispatchers. The root cause was subtle: simple object spreading creates shallow copies, so the pasted node's nested parameters still held live references to the original clipboard node's data.
Modifying the pasted subtree (renaming tasks, stripping metadata) would silently mutate the clipboard state, corrupting every subsequent paste. The fix was structuredClone:
const clonedParameters = structuredClone(copiedNode.parameters ?? {});structuredClone creates a deep, fully detached snapshot of the entire parameter tree. Every nested object, array, and primitive is recursively duplicated into new memory. The clipboard state stays immutable and safe for repeated pastes, no matter how complex the nested structure.
Conclusion: Treating the Clipboard as Managed State
The key architectural decision throughout this work was treating the clipboard as a managed state snapshot rather than a raw text string. That single framing made every other problem tractable:
- Collision prevention: Recursive renaming with a
reservedNamesSetgives every sub-task a globally unique identity. - Readable labels: Regex-based increment detection turns uniqueness enforcement into a polished UX detail.
- Ghost paste elimination: Stripping
nodePositionand triggering a layout recalculation ensures pasted nodes are always visible. - Clipboard integrity:
structuredCloneguarantees the clipboard is never corrupted across repeated pastes of complex nested structures.

Cross-workflow pasting remains the next milestone on ByteChef's roadmap. The foundation is already solid enough to support it because the hard problems were solved at the architecture level, not patched over at the surface.
Open the ByteChef Builder, build a workflow with a few nested branches, and hit copy-paste. The complexity is entirely invisible - which is exactly the point.
Subscribe to the ByteChef Newsletter
Get the latest guides on complex automation, AI agents, and visual workflow best practices delivered to your inbox.