Create a project template
Create a project template in the workspace. The top-level fields describe the template in the picker, and template_data describes the project it creates, including its starter work items in template_data.workitems.
The API checks the shape of the JSON keys in template_data before it saves anything. A failure returns 400 invalid_request, and no template is created. See Shape checks.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs, so in https://app.plane.so/my-team/projects/ the slug is my-team.
Body Parameters
name:requiredstringDisplay name of the template, shown in the template picker. Maximum 255 characters.
template_data:requiredobjectThe project seed. name is the only required key. It names the project, holds at most 255 characters, and must be unique among the workspace's project templates. Every other key is optional and takes its default when omitted:
- Project fields:
description,network(0or2),priority(none,low,medium,high, orurgent),timezone,archive_inandclose_in(0 to 12),start_date,target_date,logo_props,cover_asset,default_assignee, andproject_lead. - Feature toggles:
module_view,cycle_view,issue_views_view,page_view,intake_view,is_time_tracking_enabled,is_issue_type_enabled,is_project_updates_enabled,is_epic_enabled,is_workflow_enabled,is_milestone_enabled, andguest_view_all_features. - Seeds:
states,project_state,labels,estimates,workitem_types,epics,members,intake_settings,workflows,milestones,modules,recurring_work_items, andautomations. workitems: the starter work items. Each entry needsname(at most 255 characters) and takesdescription_html(defaults to<p></p>),priority(defaults tonone),state,assignees,labels,type,modules, andproperties.
Give each state, label, and other seed entry its own id, a UUID you choose. Work item seeds refer to them by that id, for example "state": {"id": "<a states[].id>", "name": "Backlog", "group": "backlog"}. The overview lists every key with its shape and default.
short_description:optionalstringOne-line summary of what the template is for. Nullable. null and omission are both stored as an empty string.
description_html:optionalstringLonger description of the template itself, as HTML. Defaults to <p></p>.
is_published:optionalbooleanWhether the template is published. Defaults to false. Other workspaces can copy a template only once it is published.
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 | A required field is missing, a value is outside its allowed set or range, or a template_data key or work item seed fails the shape check. |
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. |
404 | not_found | No such workspace, or it's outside your tenant. |
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/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Service launch",
"short_description": "States, labels, and starter work items for a new service",
"description_html": "<p>Use this for every new backend service.</p>",
"template_data": {
"name": "Service project",
"description": "Bootstraps a new backend service",
"network": 2,
"priority": "high",
"states": [
{ "id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15", "name": "Backlog", "color": "#a3a3a3", "sequence": 15000, "group": "backlog", "default": true },
{ "id": "c8e3f1b5-7a24-4d96-b0e1-4a6d8c2f9b37", "name": "Done", "color": "#16a34a", "sequence": 25000, "group": "completed", "default": false }
],
"labels": [
{ "id": "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54", "name": "infra", "color": "#3f76ff" }
],
"workitems": [
{
"name": "Set up CI",
"description_html": "<p>Build, test, and deploy on every merge.</p>",
"priority": "high",
"state": { "id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15", "name": "Backlog", "group": "backlog" },
"labels": [{ "id": "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54", "name": "infra", "color": "#3f76ff" }]
},
{
"name": "Write the runbook",
"priority": "low",
"state": { "id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15", "name": "Backlog", "group": "backlog" }
}
]
}
}'import requests
backlog = {"id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15", "name": "Backlog", "group": "backlog"}
infra = {"id": "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54", "name": "infra", "color": "#3f76ff"}
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/",
headers={"X-Api-Key": "your-api-key"},
json={
"name": "Service launch",
"short_description": "States, labels, and starter work items for a new service",
"description_html": "<p>Use this for every new backend service.</p>",
"template_data": {
"name": "Service project",
"description": "Bootstraps a new backend service",
"network": 2,
"priority": "high",
"states": [
{**backlog, "color": "#a3a3a3", "sequence": 15000, "default": True},
{
"id": "c8e3f1b5-7a24-4d96-b0e1-4a6d8c2f9b37",
"name": "Done",
"color": "#16a34a",
"sequence": 25000,
"group": "completed",
"default": False,
},
],
"labels": [infra],
"workitems": [
{
"name": "Set up CI",
"description_html": "<p>Build, test, and deploy on every merge.</p>",
"priority": "high",
"state": backlog,
"labels": [infra],
},
{"name": "Write the runbook", "priority": "low", "state": backlog},
],
},
},
)
print(response.json())const backlog = { id: "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15", name: "Backlog", group: "backlog" };
const infra = { id: "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54", name: "infra", color: "#3f76ff" };
const response = await fetch("https://api.plane.so/api/v2/workspaces/my-team/project-templates/", {
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Service launch",
short_description: "States, labels, and starter work items for a new service",
description_html: "<p>Use this for every new backend service.</p>",
template_data: {
name: "Service project",
description: "Bootstraps a new backend service",
network: 2,
priority: "high",
states: [
{ ...backlog, color: "#a3a3a3", sequence: 15000, default: true },
{
id: "c8e3f1b5-7a24-4d96-b0e1-4a6d8c2f9b37",
name: "Done",
color: "#16a34a",
sequence: 25000,
group: "completed",
default: false,
},
],
labels: [infra],
workitems: [
{
name: "Set up CI",
description_html: "<p>Build, test, and deploy on every merge.</p>",
priority: "high",
state: backlog,
labels: [infra],
},
{ name: "Write the runbook", priority: "low", state: backlog },
],
},
}),
});
const data = await response.json();{
"id": "5b7e3c1a-9d24-4f68-b5a0-2e8c6d4f1b97",
"name": "Service launch",
"short_description": "States, labels, and starter work items for a new service",
"description_html": "<p>Use this for every new backend service.</p>",
"template_type": "project",
"is_published": false,
"is_verified": false,
"short_id": "p7m2qa",
"slug": "service-launch",
"category_ids": [],
"template_data": {
"id": "e4a9c2f7-1b38-4d5e-9f60-7a2c8b3d1e45",
"name": "Service project",
"description": "Bootstraps a new backend service",
"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": false,
"is_time_tracking_enabled": false,
"is_issue_type_enabled": false,
"is_project_updates_enabled": false,
"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": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15",
"name": "Backlog",
"color": "#a3a3a3",
"group": "backlog",
"default": true,
"sequence": 15000
},
{
"id": "c8e3f1b5-7a24-4d96-b0e1-4a6d8c2f9b37",
"name": "Done",
"color": "#16a34a",
"group": "completed",
"default": false,
"sequence": 25000
}
],
"priority": "high",
"project_state": {},
"start_date": null,
"target_date": null,
"labels": [
{
"id": "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54",
"name": "infra",
"color": "#3f76ff"
}
],
"workflows": {},
"estimates": {},
"workitem_types": {},
"epics": {},
"members": [],
"intake_settings": {},
"milestones": {},
"modules": {},
"recurring_work_items": {},
"automations": {},
"workitems": [
{
"name": "Set up CI",
"description_html": "<p>Build, test, and deploy on every merge.</p>",
"priority": "high",
"state": {
"id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15",
"name": "Backlog",
"group": "backlog"
},
"assignees": [],
"labels": [
{
"id": "d4b7a2e9-1f63-4c85-9e02-6b3d8f1a7c54",
"name": "infra",
"color": "#3f76ff"
}
],
"type": {},
"modules": [],
"properties": []
},
{
"name": "Write the runbook",
"description_html": "<p></p>",
"priority": "low",
"state": {
"id": "a1f4c7e2-5b8d-4e19-9c63-2f7a0d4b8e15",
"name": "Backlog",
"group": "backlog"
},
"assignees": [],
"labels": [],
"type": {},
"modules": [],
"properties": []
}
]
},
"created_at": "2026-01-14T09:22:41.478363Z",
"updated_at": "2026-01-14T09:22:41.478363Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
}The shape check's errors differ from the usual list
Most 400 invalid_request responses carry errors as a list of {field, code, message} entries. A failed shape check carries errors as an object keyed by the failing template_data key instead. Branch on code and show detail, which names the work item seed by its index when one fails.

