Skip to content

Create a work item

POST/api/v2/workspaces/{slug}/projects/{project_id}/work-items/

Create a work item in a project. Only name is required — everything else falls back to the project's defaults, so the smallest useful create is one field.

Each relation accepts either its id field or a write-only human-readable parallel: state instead of state_id, assignees (emails) instead of assignee_ids, labels (names) instead of label_ids, and so on. Send one or the other, never both. This is what lets an importer or an agent write a fully-populated work item without first resolving six UUIDs.

The response is the standard sparse read shape, with custom_fields populated.

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.

project_id:requiredstring (uuid)

The project to create the work item in.

Body Parameters

name:requiredstring

Title of the work item. Maximum 255 characters. The only required field.

description_html:optionalstring

Rich-text body as HTML, for example <p>Steps to reproduce…</p>. The HTML is sanitized on the way in; content that can't be sanitized is rejected with a 400.

description_html is not part of the read shape, so it will not appear in the response.

priority:optionalstring

One of urgent, high, medium, low, none. Defaults to none.

state_id:optionalstring (uuid)

The workflow state to create the work item in. Must be a state of this project. Omit it and the work item lands in the project's default state.

state:optionalstring

The state's name instead of its id, for example In Progress. Matched case-insensitively within the project. Write-only.

Unknown name or a name that matches more than one state is a 400. Sending both state and state_id is a 400.

type_id:optionalstring (uuid)

The work item type. Accepts a type owned by the project and a workspace-level type imported into it. Requires work item types to be enabled.

type:optionalstring

The type's name instead of its id, for example Bug. Matched case-insensitively among the project's non-epic types. Write-only.

parent_id:optionalstring (uuid)

The parent work item. The parent must be in the same workspace, but it may live in a different project.

parent:optionalstring

The parent's identifier instead of its id, for example PROJ-118. Resolved within the workspace. Write-only.

assignee_ids:optionalarray of string (uuid)

User ids to assign. Each must be an active, assignable member of the project.

Leave it out and the project's default assignee is applied, if one is configured. Send [] to create the work item with no assignee at all.

assignees:optionalarray of string (email)

Member email addresses instead of ids, for example ["ana@example.com"]. Every address must belong to an active, assignable project member — any that don't are named in the 400. Write-only.

label_ids:optionalarray of string (uuid)

Label ids to apply. Each must be a label of this project, or a workspace-level label available to it.

labels:optionalarray of string

Label names instead of ids, for example ["regression", "auth"]. Matched case-insensitively against the project's labels and the workspace-level labels available to it. A name that exists at both levels is ambiguous and returns a 400 telling you to use label_ids. Write-only.

estimate_point_id:optionalstring (uuid)

The estimate point to assign, from the project's active estimate system.

estimate:optionalstring

The estimate point's value instead of its id, for example 5 or L. Resolved against the project's active estimate. Write-only.

start_date:optionalstring (date)

Planned start, for example 2026-01-12. Must not be after target_date.

target_date:optionalstring (date)

Planned due date, for example 2026-01-20.

external_id:optionalstring

Your system's identifier for this work item, for sync and import correlation. Maximum 255 characters.

Stored and filterable on List work items, but not returned on reads.

external_source:optionalstring

The system external_id came from, for example github or jira. Maximum 255 characters.

Setting custom property values

Work item type custom properties are written through a separate custom_fields object on the request body, keyed by property name, and are validated against the type you selected. Its shape depends on the type you chose, so it is not declared in the OpenAPI schema — read it from the type's schema endpoint. It requires custom properties to be enabled for the workspace — sending it otherwise is a 400. Properties you omit are filled from the type's defaults. See work item properties.

Scopes

projects.work_items:write

Errors

StatusCodeCause
400validation_errorMissing name; a bad priority; start_date after target_date; an unresolvable or ambiguous state/type/parent/assignees/labels/estimate; both a name and its *_id; or an id from another project or workspace.
401unauthorizedMissing or invalid credentials.
403forbiddenYour role or token scope can't create work items in this project.
403workflow_transition_deniedA workflow rule forbids creating a work item in the requested state.
404resource_not_foundNo such workspace or project, or it's outside your tenant.
409conflictThe write collides with a uniqueness or protected-resource constraint.
429rate_limitedThrottled. Honor the Retry-After header before retrying.
Create a work item
bash
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Fix login redirect loop",
  "priority": "high",
  "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
  "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
  "target_date": "2026-01-20"
}'
Response201
json
{
  "id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13",
  "name": "Fix login redirect loop",
  "identifier": "PROJ-142",
  "sequence_id": 142,
  "priority": "high",
  "state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
  "type_id": null,
  "assignee_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430"],
  "label_ids": [],
  "parent_id": null,
  "start_date": null,
  "target_date": "2026-01-20",
  "is_draft": false,
  "archived_at": null,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "custom_fields": {}
}
Create with names instead of ids
bash
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-items/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Fix login redirect loop",
  "state": "In Progress",
  "type": "Bug",
  "parent": "PROJ-118",
  "assignees": ["ana@example.com"],
  "labels": ["regression", "auth"],
  "estimate": "5"
}'
Response400
json
{
  "type": "https://api.plane.so/errors/validation_error",
  "title": "Validation Error",
  "status": 400,
  "code": "validation_error",
  "detail": "The request body failed validation.",
  "errors": [{ "field": "state", "message": "No state named 'In Reviewing' in this project." }]
}

Names or ids, not both

The human-readable inputs are resolved to their id equivalents before anything is written, and the rules are the same for all six:

You sendWhat happens
state_id onlyUsed directly. Must belong to this project.
state onlyResolved by name, case-insensitively, within the project.
state and state_id400 — "Provide either state_id or state, not both."
A name matching nothing400 naming the field and the value you sent.
A name matching more than one record400 telling you to use the id field instead.

Prefer the id fields in code you control and reuse across runs; prefer the names when you're bridging a system whose vocabulary is already words — a Jira status, a Slack email, a label the user typed.

Workflow rules can reject a create

If the project runs workflow rules, creating a work item directly into a restricted state returns 403 with workflow_transition_denied — distinct from a plain permission forbidden. Branch on the code: forbidden means ask for access, workflow_transition_denied means create it in an allowed state and move it from there.