Update work item type governance
Move a work item type to a new governance mode: any, constrained, or required. The response is the type's governance object after the change.
Every request names the target mode. A change that takes a workflow away from projects already using it needs acknowledge: true. A change that strands work items in states the new workflow doesn't contain needs a state_mapping. See Changing the mode and The orphan gate.
Mandating a workflow is a dry run until you acknowledge it
"mode": "required" without "acknowledge": true changes nothing. It returns 200 with a preview object that counts the work items the mandate would move. Send the same body again with "acknowledge": true to apply it.
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.
type_id:requiredstring (uuid)The work item type. It can be any type in the workspace.
Body Parameters
mode:requiredstringThe target mode. One of any, constrained, or required. Plane ignores fields that the target mode doesn't use.
any: clears the allowlist and keeps pins. Nothing else changes.constrained: replaces the allowlist withworkflow_ids.required: mandatesrequired_workflow_idfor every project that uses the type.
workflow_ids:optionalarray of string (uuid)Required when mode is constrained. The new allowlist. Every id must be an active catalog workflow in this workspace, or the request returns 400 invalid_request.
If a workflow that a project's pick uses is missing from this list, the request returns 409 workflow_allowlist_excludes_in_use unless acknowledge is true.
required_workflow_id:optionalstring (uuid)Required when mode is required. The workflow to mandate. It must be an active catalog workflow in this workspace.
acknowledge:optionalbooleanDefaults to false. Set it to true to confirm a consequence you have already previewed:
- under
constrained, dropping workflows that projects' picks use. Those projects fall back to the workspace default workflow, and Plane deletes pins outside the new allowlist. - under
required, applying the mandate instead of returning a dry run.
acknowledge doesn't bypass the orphan gate. Stranded work items still need state_mapping.
state_mapping:optionalobjectMaps a current state id to a state id in the target workflow, for work items whose state the target workflow doesn't contain. Values must be UUIDs. Plane ignores a key that isn't a valid UUID.
The target workflow is the workspace default workflow for projects a constrained change strands, and the mandated workflow for required. If the mapping doesn't cover every stranded state, or points at a state outside the target workflow, the request returns 409 workflow_state_orphans.
Scopes
workspaces.workflows:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | mode missing or outside the enum, workflow_ids missing for constrained, required_workflow_id missing for required, an unknown or inactive workflow, or a non-UUID state_mapping value. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Workspace governance isn't on your plan. |
403 | forbidden | Your role or token scope can't edit workspace workflows. |
404 | not_found | 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. |
409 | workflow_allowlist_excludes_in_use | A constrained allowlist leaves out workflows that projects' picks use, and acknowledge isn't true. The body lists them in workflows. |
409 | workflow_state_orphans | The change strands work items 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/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/governance/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "constrained",
"workflow_ids": [
"a1f4c7e2-3b98-4d61-8e25-6c0b9d7f3a14",
"e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93"
]
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/governance/",
headers={"X-Api-Key": "your-api-key"},
json={
"mode": "constrained",
"workflow_ids": [
"a1f4c7e2-3b98-4d61-8e25-6c0b9d7f3a14",
"e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
],
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/governance/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
mode: "constrained",
workflow_ids: [
"a1f4c7e2-3b98-4d61-8e25-6c0b9d7f3a14",
"e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
],
}),
},
);
const data = await response.json();{
"mode": "constrained",
"required_workflow_id": null,
"allowlist": [
{
"workflow_id": "a1f4c7e2-3b98-4d61-8e25-6c0b9d7f3a14",
"name": "Standard delivery",
"locked": true,
"in_use_by_project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b"]
},
{
"workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"name": "Regulated release",
"locked": false,
"in_use_by_project_ids": []
}
]
}{
"preview": {
"total": 12,
"per_state": [
{
"state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"count": 12
}
]
}
}{
"type": "conflict",
"code": "workflow_allowlist_excludes_in_use",
"detail": "The allowlist excludes workflows that projects are currently using. Re-send with acknowledge=true to drop those picks.",
"workflows": [
{
"id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"name": "Regulated release"
}
]
}{
"type": "conflict",
"code": "workflow_state_orphans",
"detail": "This change strands work items outside the target workflow; supply a state_mapping that covers them.",
"orphans": {
"error": "Some work items are in states that are not part of the new chain — map every affected state to a state in it.",
"code": "orphaned_states",
"orphans": [
{
"state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"state_name": "In Review",
"count": 12,
"per_project": [
{
"project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b",
"count": 12
}
]
}
],
"invalid_targets": []
}
}Mandating a workflow, step by step
- Preview the move with Preview a governance change, or send the
PATCHwithoutacknowledgeand read thepreviewobject. - Send the
PATCHwith"acknowledge": true. - If it returns
409 workflow_state_orphans, map every state inorphans.orphansto a state in the mandated workflow and send the request again with thatstate_mapping.
{
"mode": "required",
"required_workflow_id": "e6b2d9f1-48a7-4c3e-9f50-1d7a8c2b6e93",
"acknowledge": true,
"state_mapping": {
"b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f"
}
}On success, every project pick for the type points at the mandated workflow, every pin for the type is deleted, and the stranded work items move to the states you mapped. Projects can no longer change the workflow for this type. A request to set their pick returns 403 workflow_mandated.
Loosening is always safe
Moving to any never strands anything, so it needs no acknowledge and no state_mapping. It clears the allowlist and leaves every pick and pin in place.

