Skip to content

Create a project template ​

POST/api/v2/workspaces/{slug}/project-templates/

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 bundled 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:requiredstring

The workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.

Body parameters ​

name:requiredstring

Display name of the template, shown in the template picker. Maximum 255 characters.

template_data:requiredobject

The 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. Plane doesn't check this before saving, so a duplicate returns 500 server_error. Every other key is optional and takes its default when omitted:

  • Project fields: description, network (0 or 2), priority (none, low, medium, high, or urgent), timezone, archive_in and close_in (0 to 12), start_date, target_date, logo_props, cover_asset, default_assignee, and project_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, and guest_view_all_features.
  • Project content: states, project_state, labels, estimates, workitem_types, epics, members, intake_settings, workflows, milestones, modules, recurring_work_items, and automations.
  • workitems: the bundled work items. Each entry needs name (at most 255 characters) and takes description_html (defaults to <p></p>), priority (defaults to none), state, assignees, labels, type, modules, and properties.

Give each entry in these lists, such as a state or a label, its own id, a UUID you choose. Bundled work items 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:optionalstring

One-line summary of what the template is for. Nullable. null and omission are both stored as an empty string.

description_html:optionalstring

Longer description of the template itself, as HTML. Defaults to <p></p>.

is_published:optionalboolean

Whether the template is published. Defaults to false. Other workspaces can copy a template only once it is published.

Response shaping ​

fields:optionalstring

Comma-separated list of fields to return. Fields you don't request are left out of the response instead of coming back as null, so a missing key means you didn't ask for it and null means the value is null. id is always included, whether or not you name it.

Pass all to get every requestable field. An unknown field name returns 400 with the list of valid names and the closest match, so a typo is caught instead of being silently ignored.

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 ​

StatusCodeCause
400invalid_requestA required field is missing, a value is outside its allowed set or range, or a template_data key or bundled work item fails the shape check.
401unauthorizedMissing or invalid credentials.
402payment_requiredProject templates aren't in your plan, or the feature is switched off for this workspace.
403forbiddenYour role or token scope can't create project templates.
404not_foundNo such workspace, or it's outside your tenant.
406not_acceptableThe Accept header asks for a representation the API can't produce.
409governance_migration_in_progressThe workspace is migrating to workspace governance. Writes are blocked until it finishes, so retry later.
413payload_too_largeThe request body is over the size limit.
415unsupported_media_typeThe Content-Type isn't one this endpoint accepts.
429rate_limitedThrottled. Honor the Retry-After header before retrying.
500server_errortemplate_data.name matches another project template in this workspace.
Create a project template
bash
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" }
      }
    ]
  }
}'
Response201
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 bundled work item by its index when one fails.