Create a project from a template
Create a new project in this workspace from one of the workspace's project templates. The body takes project create fields. You name the project and give it an identifier, and the template supplies its states, feature toggles, and starter content.
The response is the new project, with 201. Plane creates the project, its members, and its work item states before it responds. Labels, estimates, work item types, modules, and the starter work items come from a background job that runs after the response, so they can take a moment to appear. See What the template applies.
The template doesn't need to be published, but it must belong to this workspace. To use a template published by another workspace, copy it in first.
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.
pk:requiredstring (uuid)The id of the project template to use. A template_id in the body is ignored, and this path id always wins.
Body Parameters
name:requiredstringName of the new project, unique within the workspace. Maximum 255 characters. It can't contain <, >, ", {, }, [, ], *, ^, !, #, %, or \.
identifier:requiredstringShort project key used to prefix work item numbers, for example PAY in PAY-12. Unique within the workspace, and stored in upper case. Punctuation such as -, ., @, and # isn't allowed.
description:optionalstringPlain-text description of the project. When you send only description, Plane builds description_html from it.
description_html:optionalstringDescription as HTML. Plane sanitizes it and fills description from it when you don't send one.
network:optionalintegerProject visibility. 0 is private to members, 2 is visible to the whole workspace. Defaults to 2.
project_lead:optionalstring (uuid)User id of the project lead, who must be a workspace member. The lead joins the project as an admin. This endpoint takes project_lead, not the project_lead_id that Create a project takes.
default_assignee:optionalstring (uuid)User id of the default assignee, who must be a workspace member. Named default_assignee here, not default_assignee_id.
state_id:optionalstring (uuid)The project's lifecycle state. It must belong to this workspace. When the workspace uses project grouping and the template sets project_state, the template's state replaces this one.
priority:optionalstringProject priority. One of none, low, medium, high, or urgent.
start_date:optionalstringPlanned start date, as YYYY-MM-DD or a full ISO 8601 date-time. Nullable.
target_date:optionalstringPlanned due date, as YYYY-MM-DD or a full ISO 8601 date-time. Nullable.
timezone:optionalstringIANA time zone, for example Europe/Berlin. Defaults to UTC.
archive_in:optionalintegerMonths before completed work items are auto-archived, from 0 to 12. 0 turns it off.
close_in:optionalintegerMonths before stale work items are auto-closed, from 0 to 12. 0 turns it off.
logo_props:optionalobjectEditor-owned descriptor for the project icon. When you omit it, Plane picks a random icon and color.
cover_image:optionalstringURL of a cover image. When you send one, the template's cover isn't copied.
emoji:optionalstringEmoji shown with the project name. Maximum 255 characters. Nullable.
icon_prop:optionalobjectLegacy icon descriptor. Nullable.
external_id:optionalstringYour system's identifier for this project, for sync and import correlation. Maximum 255 characters.
external_source:optionalstringThe system external_id came from, for example jira. Maximum 255 characters.
The body also accepts module_view, cycle_view, issue_views_view, page_view, intake_view, is_issue_type_enabled, and is_time_tracking_enabled, but the template's values replace them. These toggles always come from template_data.
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: archive_in, archived_at, close_in, cover_image, cover_image_url, created_at, created_by_id, cycle_view, default_assignee_id, default_state_id, description, description_html, emoji, estimate_id, external_id, external_source, guest_view_all_features, icon_prop, id, identifier, intake_view, is_issue_type_enabled, is_time_tracking_enabled, issue_views_view, logo_props, module_view, name, network, page_view, priority, project_lead_id, start_date, state_id, target_date, timezone, updated_at, updated_by_id.
See Sparse fields.
expand:optionalstringComma-separated relations to embed alongside the ids: default_assignee (the default assignee's user) and project_lead (the lead's user).
Expansion is separate-key: ?expand=project_lead keeps project_lead_id and adds a project_lead object next to it. An unknown value is a 400. See Expanding relations.
Scopes
templates.projects:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | name or identifier is missing or has a forbidden character, the identifier is taken or too long, the lead or default assignee isn't a workspace member, or state_id belongs to another workspace. |
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 use project templates, or you can't create projects in this workspace. You need both. |
404 | not_found | No project template with this id in this workspace. |
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/5b7e3c1a-9d24-4f68-b5a0-2e8c6d4f1b97/use/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Payments",
"identifier": "PAY",
"description": "Card and bank payment flows",
"project_lead": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}'import requests
response = requests.post(
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/5b7e3c1a-9d24-4f68-b5a0-2e8c6d4f1b97/use/",
headers={"X-Api-Key": "your-api-key"},
json={
"name": "Payments",
"identifier": "PAY",
"description": "Card and bank payment flows",
"project_lead": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
},
)
project = response.json()
print(project["id"], project["identifier"])const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/project-templates/5b7e3c1a-9d24-4f68-b5a0-2e8c6d4f1b97/use/",
{
method: "POST",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Payments",
identifier: "PAY",
description: "Card and bank payment flows",
project_lead: "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
}),
},
);
const project = await response.json();{
"id": "6d2a8f4c-0b7e-4a19-93c5-e8f1b6d4a207",
"name": "Payments",
"description": "Card and bank payment flows",
"description_html": "<p>Card and bank payment flows</p>",
"identifier": "PAY",
"network": 2,
"project_lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"default_assignee_id": null,
"default_state_id": null,
"estimate_id": null,
"state_id": "0e5b9c3a-7d21-4f86-b4a8-2c6f1e9d7b30",
"priority": "none",
"start_date": null,
"target_date": null,
"emoji": null,
"icon_prop": null,
"logo_props": {
"in_use": "icon",
"icon": {
"name": "rocket",
"color": "#5e6ad2"
}
},
"cover_image": null,
"cover_image_url": null,
"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,
"guest_view_all_features": false,
"archive_in": 0,
"close_in": 0,
"timezone": "UTC",
"archived_at": null,
"external_id": null,
"external_source": null,
"created_at": "2026-02-03T16:45:12.904117Z",
"updated_at": "2026-02-03T16:45:13.118402Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": null
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "One or more fields failed validation.",
"errors": [
{
"field": "non_field_errors",
"code": "identifier_already_taken",
"message": "Project Identifier is taken"
}
]
}What the template applies
Before the response, Plane:
- creates the project from your body and adds you to it as an admin, along with the project lead when the lead is someone else;
- creates the template's
statesas the project's work item states; - copies the feature toggles
module_view,cycle_view,issue_views_view,page_view,intake_view,is_time_tracking_enabled, andis_issue_type_enabledfrom the template and replaces any you sent; - switches project updates, epics, and workflows on or off to match
is_project_updates_enabled,is_epic_enabled, andis_workflow_enabled; - sets up intake from
intake_settingswhenintake_viewis on; - copies the template's cover image when it is stored in Plane, unless you sent
cover_image; - sets the project's lifecycle state from
project_state, when the workspace uses project grouping and that state still exists.
After the response, a background job:
- creates the template's
labelsandestimates; - creates the template's
workitem_typesandepics, or links the workspace's default work item type when the workspace manages work item types at the workspace level; - creates the template's
moduleswhenmodule_viewis on; - creates one work item per entry in
workitems, mapped onto the new states, labels, types, and modules by their template ids.
The project's other settings, such as network, timezone, priority, and its dates, come from your request body, not from the template.
Poll for the starter work items
The background job runs after you get the 201, so an immediate List work items on the new project can return an incomplete list. If your integration needs the seeded work items, poll the list until the count matches the template's workitems.

