Copy a project template
Copy a published project template into this workspace as a new template that the workspace owns. The source can live in this workspace or in any other one, as long as it is published. The source id goes in the body instead of the path, because the source usually isn't a template that this workspace's routes can address.
The response is the new template in full, with 201. Edit or use it like any other template in the workspace. Later changes to the source don't reach the copy.
Path Parameters
slug:requiredstringThe workspace to copy the template into. It appears in your Plane URLs, so in https://app.plane.so/my-team/projects/ the slug is my-team.
Body Parameters
template_id:requiredstring (uuid)The id of the project template to copy. It must have is_published: true. An unknown id returns 404, and so does the id of an unpublished template, even one in this workspace.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: category_ids, created_at, created_by_id, description_html, id, is_published, is_verified, name, short_description, short_id, slug, template_data, template_type, updated_at, updated_by_id.
See Sparse fields.
Scopes
templates.projects:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | template_id is missing or isn't a valid UUID. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Project templates aren't in your plan, or the feature is switched off for this workspace. |
403 | forbidden | Your role or token scope can't create project templates in this workspace. |
404 | not_found | No such workspace, or no published project template has this id. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | governance_migration_in_progress | The workspace is migrating to workspace governance. Writes are blocked until it finishes, so retry later. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X POST \
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/copy/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "8c3f6a1d-2e7b-4d95-a4c0-6f1b9e3d7a28"
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/copy/",
headers={"X-Api-Key": "your-api-key"},
json={"template_id": "8c3f6a1d-2e7b-4d95-a4c0-6f1b9e3d7a28"},
)
print(response.json()["id"])const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/copy/",
{
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({ template_id: "8c3f6a1d-2e7b-4d95-a4c0-6f1b9e3d7a28" }),
},
);
const data = await response.json();{
"id": "2f9d4b7e-6a13-4c58-8e21-b0d7c5a3f946",
"name": "Product launch",
"short_description": "Phases and states for shipping a new product",
"description_html": "<p></p>",
"template_type": "project",
"is_published": false,
"is_verified": false,
"short_id": "w4n7ub",
"slug": "product-launch",
"category_ids": [],
"template_data": {
"id": "7e1b5c9a-3d46-4f82-b9a0-1c6e8d2f4b73",
"name": "Product launch",
"description": "From beta to general availability",
"network": 2,
"default_assignee": {},
"project_lead": {},
"logo_props": {},
"cover_asset": "",
"module_view": true,
"cycle_view": true,
"issue_views_view": true,
"page_view": true,
"intake_view": true,
"is_time_tracking_enabled": false,
"is_issue_type_enabled": false,
"is_project_updates_enabled": true,
"is_epic_enabled": false,
"is_workflow_enabled": false,
"is_milestone_enabled": false,
"guest_view_all_features": false,
"timezone": "UTC",
"archive_in": 0,
"close_in": 0,
"states": [
{
"id": "f3a8d1c6-9b25-4e70-8d43-5c2a7e9b1f08",
"name": "Planned",
"color": "#a3a3a3",
"group": "unstarted",
"default": true,
"sequence": 15000
},
{
"id": "4b9e2f7a-1c58-4d36-a7e0-9f3d6b1c8e25",
"name": "Launched",
"color": "#16a34a",
"group": "completed",
"default": false,
"sequence": 25000
}
],
"priority": "medium",
"project_state": {},
"start_date": null,
"target_date": null,
"labels": [],
"workflows": {},
"estimates": {},
"workitem_types": {},
"epics": {},
"members": [],
"intake_settings": {
"is_form_enabled": true,
"is_email_enabled": false,
"is_in_app_enabled": true
},
"milestones": {},
"modules": {},
"recurring_work_items": {},
"automations": {},
"workitems": []
},
"created_at": "2026-02-03T16:45:12.904117Z",
"updated_at": "2026-02-03T16:45:12.904117Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
}{
"type": "not_found",
"code": "not_found",
"detail": "No published project template matches that id."
}What the copy keeps
The copy is a new template in this workspace with a new id and short_id. From the source it keeps:
nameandshort_description.- The whole
template_dataseed, except the keys listed below. - The cover. An external cover URL is reused as is. A cover image stored in Plane is duplicated as a new asset.
What the copy drops
description_htmlresets to<p></p>.is_publishedisfalse,is_verifiedisfalse, andcategory_idsis empty.template_data.default_assignee,project_lead, andproject_statereset to{}, andmembersresets to[]. Users and lifecycle states are specific to the source workspace.- The default value of every
RELATIONproperty inworkitem_typesandepicsresets to[]. template_data.workitemsis empty. The bundled work items stay with the source. If you can read the source template, send itsworkitemsin an update of the copy.
Naming
When this workspace already has a project template whose name starts with the source's name, the copy takes the newest such template's name plus (copy). Copying Product launch twice gives Product launch and then Product launch (copy). template_data.name follows the same rule against the workspace's other project seeds.

