How to duplicate a project
Last updated: July 14, 2026
A step-by-step guide to cloning an existing Plane project (including all work items, relations and dependencies) into a brand new project.
Duplicating a project is not a built-in feature of the Plane product. This guide uses Plane Compose, Plane's official command-line companion tool, to achieve it. If you'd like to learn more about what Plane Compose can do, see the Plane Compose documentation.
What you'll accomplish
You'll take an existing Plane project and create a complete copy of it under a new project key, preserving all work items, parent/child hierarchies, module assignments, and inter-item relations.
This requires two separate push operations. The first creates all the work items in your new project. The second links the relations; this two-step approach is necessary because relation targets need to exist before they can be linked.
Part 1: One-time setup
If you've already used Plane Compose, skip ahead to Part 2. Otherwise, work through this section once.
1.1 Open a terminal
All the commands in this guide are typed into a terminal.
macOS: Press
Cmd + Space, typeTerminal, and press Enter.Windows: Open the Start menu, type
PowerShell, and press Enter.
1.2 Install the tools
Follow the block for your operating system, running the commands one at a time, in order.
macOS
First install Homebrew (a tool that installs other tools). This command will ask for your Mac login password. The screen shows nothing while you type it; that's normal, just type it and press Enter:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
When it finishes, it may print a couple of commands under "Next steps". Copy, paste, and run those too. Then:
brew install pipx
pipx ensurepath
Windows
First install Python: go to python.org/downloads, download the installer, and run it. Important: on the first screen of the installer, tick the checkbox that says "Add Python to PATH" before clicking Install.
Then, in PowerShell:
pip install --user pipx
pipx ensurepath
Windows note: anywhere this guide says python3, type python instead; that's the command name on Windows.
Linux
pip install --user pipx
pipx ensurepath
(If pip isn't found, install it first, e.g. sudo apt install python3-pip on Ubuntu.)
1.3 Install Plane Compose
Close your terminal window and open a new one. This makes the changes from pipx ensurepath take effect; skipping this causes "command not found" errors. Then run:
pipx install plane-compose
Verify it worked:
plane --version
You should see a version number. If you see command not found: plane, close and reopen the terminal once more and try again.
1.4 Install PyYAML
Steps 3 and 7 of this guide use a small Python helper that needs one extra piece installed:
python3 -m pip install --user pyyaml
If this fails with an error mentioning "externally-managed-environment", run this instead:
python3 -m pip install --user pyyaml --break-system-packages
1.5 Connect to your Plane account
You need an API token, a secret key that lets Plane Compose act on your behalf:
Open Plane in your browser and log in.
Go to your avatar > Settings > Personal Access Tokens
Create a new token and copy it right away; it's only shown once.
Now, back in the terminal:
plane auth login
It will ask a few questions: accept the default server URL by pressing Enter (unless your company runs its own Plane server), paste your token when asked, and enter your workspace slug (explained in Part 2 below if you're unsure).
Verify you're connected:
plane auth list-connections
You should see your connection listed. Setup is done; you never need to repeat Part 1.
Part 2: Find your workspace and project details
You need three pieces of information. All of them are visible in your browser's address bar when you're looking at the project in Plane:
https://app.plane.so/my-workspace/projects/...
^^^^^^^^^^^^
workspace slug
Workspace slug: the first part of the URL after
app.plane.so/(e.g.my-workspace).Source project key: the short prefix on the project's work items (e.g.
MOBILEinMOBILE-42). It's also shown next to the project name in Plane.
Throughout the steps below, replace MOBILE, MOBILE2, and my-workspace with your own values.
Part 3: Duplicate the project
Step 1 below moves your terminal into the project's folder, and every later step assumes you're still there. If you close the terminal partway through, open a new one and run cd mobile (using your project key, in lowercase) before continuing.
Step 1: Clone the source project
This downloads the project's schema, settings, and all work items into a local folder on your computer.
plane clone MOBILE --workspace my-workspace
MOBILE is the id of your project.
Compose creates a folder named after the project key in lowercase. Move into it:
cd mobile
Inside this folder you'll find plane.yaml (project settings), a schema/ directory (states, labels, types), and work/workitems.yaml with all the work items already inside.
Step 2: Back up the work items file
You're about to temporarily strip relations from the file. Keep a copy so you can restore it later.
cp work/workitems.yaml work/workitems_with_relations.yaml
Step 3: Strip relations for the first push
Relations in the file reference items by their source project identifiers (e.g. MOBILE-5). Those identifiers don't exist yet in the new project, so a first push with relations present would silently skip them. This command removes them for now; you'll restore them in Step 7.
Paste the entire block below as one command (including the quote marks on the first and last lines) and press Enter:
python3 -c "
import yaml
with open('work/workitems.yaml') as f:
data = yaml.safe_load(f)
def strip(item):
if isinstance(item, dict):
item.pop('dependencies', None)
item.pop('relations', None)
for child in item.get('children', []):
strip(child)
return item
if isinstance(data, dict) and 'workitems' in data:
data['workitems'] = [strip(i) for i in data['workitems']]
elif isinstance(data, list):
data = [strip(i) for i in data]
with open('work/workitems.yaml', 'w') as f:
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
"
The command prints nothing when it succeeds; no news is good news.
Why strip first? Relations are stored as MOBILE-N identifiers. On a first push to a brand new project, those identifiers aren't in Compose's lookup map yet, so any relation link would fail. After items are created in the new project, the map is populated and the second push can resolve them correctly.
Step 4: Configure plane.yaml for the new project
Now edit one file. Open plane.yaml in a text editor:
macOS: run
open -e plane.yamlin the terminal; it opens in TextEdit.Windows: run
notepad plane.yamlin PowerShell.If you have an editor like VS Code, feel free to use that instead.
Update the marked fields so they point to your new (not yet created) project:
IMPORTANT
Clear the uuid field in the plane.yaml file, so that it does not overwrite the original project.
type: project
workspace: my-workspace
project:
key: MOBILE2 # change to your new project key
uuid: "" # clear this; it will be filled in after push
name: Mobile App v2 # any name you like
description: ""
network: public
timezone: UTC
Then save the file and close the editor.
Careful with spacing: this file format is indentation-sensitive. Only change the text after the colons; don't add or remove spaces at the start of any line, and don't let the editor change straight quotes " into curly ones.
Tip: The project key (e.g. MOBILE2) becomes the prefix for all new items: MOBILE2-1, MOBILE2-2, etc. Pick something meaningful.
Step 5: Push the schema to create the new project
This creates the project in Plane and pushes all schema settings: states, labels, types, workflows, and features. The new project will be an exact copy of the source schema.
plane schema push --force
Work Item Types error? If you see 400: Work item type not found in this project, go to your new project in Plane > Settings > Features > enable Work Item Types, then re-run this command.
Step 6: First push, create all work items
This creates every work item in the new project. Since relations were stripped in Step 3, nothing will fail. After this completes, Compose's state file records the mapping from each source identifier to its new UUID in the new project.
plane push --work-only --force
You should see all items created with a success summary. Module assignments, parent/child hierarchies, assignees, labels, and custom properties are all applied in this pass.
Step 7: Restore the file with relations
Bring back the backup you made in Step 2. This reintroduces the dependencies and relations fields into the file:
cp work/workitems_with_relations.yaml work/workitems.yaml
The restored file still carries leftover id labels from the old project. Remove them so they can't conflict with the new project's numbering. Paste this entire block as one command, just like in Step 3:
python3 -c "
import yaml
with open('work/workitems.yaml') as f:
data = yaml.safe_load(f)
def strip(item):
if isinstance(item, dict):
item.pop('id', None)
for child in item.get('children', []):
strip(child)
return item
if isinstance(data, dict) and 'workitems' in data:
data['workitems'] = [strip(i) for i in data['workitems']]
elif isinstance(data, list):
data = [strip(i) for i in data]
with open('work/workitems.yaml', 'w') as f:
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
"
Compose now detects a content change (the restored relations produce a different file than what was recorded after Step 6). This is what causes the next push to run updates rather than skip everything.
Step 8: Second push, link all relations
Now that the state file has the full identifier map (each MOBILE-N mapped to its UUID in the new project), Compose can resolve every relation target. Each item gets updated with its dependencies and relations.
plane push --work-only --force
Done. Your duplicate project now has all work items with their relations fully linked. Verify in Plane by opening any item that had a dependency; the linked items should show the new project's identifiers (e.g. MOBILE2-5).
Why two pushes?
When Compose pushes work items, it resolves relation targets (like MOBILE-5) by looking them up in an identifier map, a table that records which local item ID corresponds to which UUID in Plane. On a brand-new project, this map starts empty.
As items are created during the first push, each one gets registered in the map under the new project's identifiers (MOBILE2-1, MOBILE2-2, etc.), not the source identifiers. Since items are created in parallel, there's no guarantee item B is registered before item A tries to link to it. Even if it were, the lookup key is MOBILE-5 but the registered key is MOBILE2-5; they don't match.
After the first push, the state file has a complete picture: the new UUID for every MOBILE-N item. On the second push, that map is pre-loaded before any work begins, so when Compose processes blocked_by: [MOBILE-5], it finds the UUID immediately and creates the link correctly.
Clone > Back up > Strip relations > Push 1: create items > Restore relations > Push 2: link relations
Troubleshooting
command not found: plane The terminal hasn't picked up the new installation yet. Close the terminal window entirely, open a new one, and try again. If it persists, re-run pipx ensurepath, then restart the terminal once more.
ModuleNotFoundError: No module named 'yaml' (Step 3 or 7) PyYAML isn't installed. Run python3 -m pip install --user pyyaml (see setup step 1.4), then re-run the command that failed.
No such file or directory: work/workitems.yaml Your terminal isn't inside the project folder. Run cd mobile (your project key, in lowercase) and try again.
Schema push fails with "Work item type not found in this project" The Work Item Types feature is disabled for the new project. Go to Plane > your new project > Settings > Features > toggle Work Item Types on. Then re-run plane schema push --force.
Second push says "Nothing to push" and relations aren't created The restore in Step 7 didn't change the file; the backup may not have contained any relations. Verify with:
grep -c "blocked_by\|relates_to\|duplicate" work/workitems_with_relations.yaml
If the count is 0, the source project had no relations to begin with.
Some relations are missing after the second push Run plane push --work-only --force a third time; it's safe to repeat and will resolve any stragglers from parallel processing races.
Items appear duplicated in the new project This happens if plane state clear-items was run between pushes. Do not clear state; Compose uses it to track which items already exist. Delete the duplicates from the Plane UI, then run plane pull --work-only --force to re-sync state before pushing again.
The clone command can't find the project Try using the full project UUID instead of the key:
plane clone <uuid> --workspace=my-workspace
You can find the UUID in the Plane project URL. Or run plane clone --workspace=my-workspace without a project argument for an interactive picker.