Update a work item type workflow
Set the project's pick for one work item type. The pick is the catalog workflow that the project's work items of that type follow. The response is the type's updated work item type workflow entry.
Work items of the type whose state the new workflow doesn't contain have to move. If any exist, send a state_mapping that covers their states, or the request returns 409 workflow_state_orphans and changes nothing. Add ?dry_run=true first to see which states need mapping.
The workspace can take this decision away from the project. Under required mode, the request returns 403 workflow_mandated. When the workspace has pinned the type's workflow for this project, it returns 403 workflow_assigned_by_workspace. See Work item type governance.
PATCH, not PUT
v1 sets the pick with PUT on the same path. v2 accepts the same body on PATCH.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
project_id:requiredstringThe project. Accepts the project UUID or its bare identifier, for example ENG.
type_id:requiredstring (uuid)The work item type whose workflow you are setting.
Query Parameters
dry_run:optionalbooleanSet to true to preview the change instead of applying it. The request runs every check up to the move, including the 403 and 400 refusals, then returns a preview and writes nothing. Any other value applies the change.
The preview has three fields:
total: how many work items of this type would move.type_total: how many work items of this type the project has.per_state: one entry per state the moving work items sit in now, withstate_id,state_name,state_group,count, andtarget_state_id.target_state_idis the new workflow's default state, because the preview ignoresstate_mapping.
Body Parameters
workflow_id:requiredstring (uuid)The catalog workflow to use. It must be active. Under constrained mode, it must also be on the type's allowlist, or the request returns 400 workflow_outside_allowlist.
state_mapping:optionalobjectMaps a current state id to a state id in the new workflow, for work items whose state the new workflow doesn't contain. Values must be UUIDs. Plane ignores a key that isn't a valid UUID. Every stranded state needs an entry, and every target must be in the new workflow. See The orphan gate.
Scopes
projects.workflows:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | workflow_id missing or not a UUID, an unknown or inactive workflow, or a non-UUID state_mapping value. |
400 | workflow_outside_allowlist | The type is in constrained mode and the workflow isn't on its allowlist. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Multiple workflows aren't on your plan. |
403 | forbidden | Your role or token scope can't edit work item types in this project. |
403 | workflow_mandated | The type is in required mode, so the workspace decides its workflow. |
403 | workflow_assigned_by_workspace | The workspace pinned the type's workflow for this project. |
404 | not_found | No such project, or no such work item type in this workspace. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | states_managed_at_project | The workspace doesn't have governance turned on, so the project owns its workflows. |
409 | workflow_state_orphans | Work items would be stranded and state_mapping doesn't cover their states. The body carries the orphans report. |
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 PATCH \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/workflow/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"state_mapping": {
"b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f"
}
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b"
"/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/workflow/",
headers={"X-Api-Key": "your-api-key"},
json={
"workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"state_mapping": {
"b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
},
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b" +
"/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/workflow/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
workflow_id: "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
state_mapping: {
"b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
},
}),
},
);
const data = await response.json();{
"type_id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240",
"governance": "constrained",
"allowlist_count": 2,
"allowlist_total": 3,
"effective_workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"effective_workflow_name": null,
"source": "pick",
"options": [
{
"workflow_id": "a1f4c7e2-3b98-4d61-8e25-6c0b9d7f3a14",
"name": "Standard delivery"
},
{
"workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"name": "Regulated release"
}
]
}{
"total": 7,
"type_total": 42,
"per_state": [
{
"state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"state_name": "In Review",
"state_group": "started",
"count": 7,
"target_state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48"
}
]
}{
"type": "forbidden",
"code": "workflow_mandated",
"detail": "This type's workflow is mandated by the workspace."
}{
"type": "forbidden",
"code": "workflow_assigned_by_workspace",
"detail": "This type's workflow is pinned by the workspace."
}What a successful change does
- Plane writes the project's pick for the type to
workflow_id. - Work items of the type in states outside the new workflow move to the states you mapped. Plane records each move on the work item. Archived work items move too. Drafts and work items in
triagegroup states don't. - The response is the type's entry, now with
source: "pick"and the neweffective_workflow_id.
When the type isn't active in the project, Plane still writes the pick, and the response is only {"workflow_id": "<the workflow you sent>"}.
Map from the dry run
Run the request with ?dry_run=true, map every state_id in per_state to a state in the new workflow, then send the real request with that state_mapping. The dry run reports a target_state_id for each state, but the real request doesn't fall back to it. It needs the mapping.

