Update a workspace workflow
Change a catalog workflow. The request works in one of two modes, chosen by whether the body has a states key:
- Metadata update (no
states). Changename,description, oris_active. Fields you omit keep their current values. - Chain replacement (
statespresent). Replace the whole chain in one call. Plane removes the states you leave out, adds new ones, and rewrites the order, the default, andallow_issue_creationon every entry. Plane also appliesnameanddescriptionfrom the same body, and ignoresis_activein this mode.
A metadata update never touches the chain, so a rename can't reshape a chain by accident.
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.
pk:requiredstring (uuid)The id of the catalog workflow. A project workflow's id returns 404.
Body Parameters
Every field is optional. Send states only when you want to replace the chain.
name:optionalstringNew display name, unique among the workspace's catalog workflows. Maximum 255 characters.
description:optionalstringFree-form description of the workflow. Nullable.
is_active:optionalbooleanSet to true to activate the workflow, which turns on enforcement of its rules. Activation needs at least one state in the chain, or the request returns 400. Set to false to deactivate it. Ignored when the body has states.
states:optionalarray of objectThe complete new chain, in order. Each entry is an object:
state_idstring (uuid), required. A catalog state of this workspace. Plane rejects project states, triage states, and other workspaces' states.sequencenumber, optional. Position weight, lower first. Defaults to the entry's position times 10000, so the first entry gets10000.allow_issue_creationboolean, optional. Whether new work items can be created in the state. Defaults totrue, even for a state that is already in the chain.
Each state_id can appear only once. A state that stays in the chain keeps its type. A newly added state gets type: "transition".
default_state_id:optionalstring (uuid)Required with states. The catalog state new work items land in. It must be one of the states entries.
state_mapping:optionalobjectWhere to move work items whose state leaves the chain. Each key is a catalog state id that work items sit in, and each value is a catalog state id in the new chain. Needed whenever the new chain strands work items. See The orphan gate.
A chain replacement leaves transitions alone
Unlike Remove a state from a workspace workflow, a chain replacement doesn't check transitions. Transitions that start from, target, or reject to a state you drop stay in place. Delete them first with Delete a workspace workflow transition.
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: created_at, created_by_id, description, id, is_active, is_default, name, project_ids, projects_count, referenced_resources, states, states_count, updated_at, updated_by_id, work_item_type_ids, work_item_types_count.
See Sparse fields.
Scopes
workspaces.workflows:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | Activation of a workflow with no states, a field over its limit, or an invalid chain: an empty states, an entry without state_id, a repeated state, a default_state_id outside the chain, or a state that isn't a catalog state of this workspace. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | Workflows isn't on your plan, or is switched off. |
403 | forbidden | Your role or token scope can't edit workspace workflows. |
404 | not_found | No such workflow in this workspace's catalog, or it's outside your tenant. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | Another catalog workflow already uses the new name. |
409 | governance_migration_in_progress | A governance migration is running in this workspace, so writes are paused. Retry after it finishes. |
409 | states_managed_at_project | The workspace doesn't have governance turned on. Use the project's workflows instead. |
409 | workflow_state_orphans | The new chain drops states that work items still sit in, and state_mapping doesn't cover them all or maps to a state outside the new chain. |
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. |
Chain errors carry an object
Most 400 responses list field errors in an errors array. An invalid chain returns errors as an object with a single error message instead, as in the example.
curl -X PATCH \
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"is_active": true
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
headers={"X-Api-Key": "your-api-key"},
json={
"is_active": True,
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
is_active: true,
}),
},
);
const data = await response.json();{
"id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88",
"name": "Delivery",
"description": "Engineering delivery flow with a review gate",
"is_active": true,
"is_default": false,
"states_count": 4,
"projects_count": 2,
"work_item_types_count": 1,
"states": [
{
"id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"allow_issue_creation": true,
"is_default": true,
"sequence": 10000.0,
"type": "transition",
"transitions": [
{
"id": "8d2f6a41-5c3e-4b97-a1d0-7e9c4b2f8a63",
"transition_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
"rejection_state_id": null,
"member_ids": [],
"required_approvals": null,
"pre_rules": [],
"post_rules": []
}
]
},
{
"id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
"allow_issue_creation": true,
"is_default": false,
"sequence": 20000.0,
"type": "transition",
"transitions": [
{
"id": "d7a3f5c9-0b2e-4d81-b6f4-9e1c3a5d7b20",
"transition_state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"rejection_state_id": null,
"member_ids": [],
"required_approvals": null,
"pre_rules": [
{
"id": "9a1c5e37-8b2d-4f60-a4e9-6c3b7d0f1e28",
"handler_name": "run_script",
"rule_type": "validation",
"config": {
"script_id": "5d8e2a6c-3f1b-4a97-8c04-2e9b7d1f6a35",
"execution_variables": {
"min_estimate": "1"
}
}
}
],
"post_rules": [
{
"id": "e4b7c9a2-3d1f-4e68-8a52-9f0c6b3d7e15",
"handler_name": "send_webhook",
"rule_type": "action",
"config": {
"url": "https://hooks.example.com/plane",
"headers": [
{
"key": "X-Team",
"is_secret": false,
"value": "platform"
}
],
"secret": "plane_wh_••••7c1d"
}
}
]
}
]
},
{
"id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"allow_issue_creation": false,
"is_default": false,
"sequence": 30000.0,
"type": "approval",
"transitions": [
{
"id": "4b6e1d93-2a7f-4c58-9e30-1d8b5f7a2c46",
"transition_state_id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"rejection_state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
"member_ids": ["a3f92c71-6e4b-4d08-9b5a-1c7e3d9f2b84"],
"required_approvals": 1,
"pre_rules": [],
"post_rules": []
}
]
},
{
"id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"allow_issue_creation": false,
"is_default": false,
"sequence": 40000.0,
"type": "transition",
"transitions": []
}
],
"referenced_resources": {
"states": {
"2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48": {
"id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"name": "Backlog",
"color": "#8b8d98",
"group": "backlog",
"sequence": 15000.0,
"default": false,
"project_id": null,
"workspace_id": "b1c4e7a2-5d8f-4a3b-9c6e-0f2d4a7b8c91"
},
"f960d3c2-8524-4a41-b8eb-055ce4be2a7f": {
"id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
"name": "In Progress",
"color": "#3f76ff",
"group": "started",
"sequence": 25000.0,
"default": false,
"project_id": null,
"workspace_id": "b1c4e7a2-5d8f-4a3b-9c6e-0f2d4a7b8c91"
},
"b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53": {
"id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"name": "In Review",
"color": "#f5a623",
"group": "started",
"sequence": 35000.0,
"default": false,
"project_id": null,
"workspace_id": "b1c4e7a2-5d8f-4a3b-9c6e-0f2d4a7b8c91"
},
"7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613": {
"id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"name": "Done",
"color": "#26a05f",
"group": "completed",
"sequence": 45000.0,
"default": false,
"project_id": null,
"workspace_id": "b1c4e7a2-5d8f-4a3b-9c6e-0f2d4a7b8c91"
}
},
"users": {
"a3f92c71-6e4b-4d08-9b5a-1c7e3d9f2b84": {
"id": "a3f92c71-6e4b-4d08-9b5a-1c7e3d9f2b84",
"display_name": "priya",
"avatar_url": null,
"is_bot": false,
"first_name": "Priya",
"last_name": "Raman",
"workspace_id": "b1c4e7a2-5d8f-4a3b-9c6e-0f2d4a7b8c91"
}
}
},
"project_ids": ["4af68566-94a4-4eb3-94aa-50dc9427067b", "d1e8a3c5-7f2b-4a96-8e04-3b6c9d1f5a27"],
"work_item_type_ids": ["2d9d1a97-5c6f-4a1e-9d5b-8c2f7e30b6a4"],
"created_at": "2026-01-14T09:22:41.478363Z",
"updated_at": "2026-02-03T16:45:12.904117Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"updated_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}curl -X PATCH \
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"states": [
{
"state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48"
},
{
"state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f"
},
{
"state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"allow_issue_creation": false
},
{
"state_id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"allow_issue_creation": false
}
],
"default_state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"state_mapping": {
"e5a1c8d3-4b7f-4e29-a6d0-2c9f5b8e1a74": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53"
}
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
headers={"X-Api-Key": "your-api-key"},
json={
"states": [
{
"state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
},
{
"state_id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
},
{
"state_id": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
"allow_issue_creation": False,
},
{
"state_id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"allow_issue_creation": False,
},
],
"default_state_id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"state_mapping": {
"e5a1c8d3-4b7f-4e29-a6d0-2c9f5b8e1a74": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
},
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/workflows/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
states: [
{
state_id: "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
},
{
state_id: "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
},
{
state_id: "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
allow_issue_creation: false,
},
{
state_id: "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
allow_issue_creation: false,
},
],
default_state_id: "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
state_mapping: {
"e5a1c8d3-4b7f-4e29-a6d0-2c9f5b8e1a74": "b7e42a19-3c5d-4f80-9a26-8d1c0f4e7b53",
},
}),
},
);
const data = await response.json();{
"type": "invalid_request",
"code": "invalid_request",
"detail": "Invalid workflow chain.",
"errors": {
"error": "default_state_id must be one of the chain states"
}
}{
"type": "conflict",
"code": "workflow_state_orphans",
"detail": "Removing these states strands work items; 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": "e5a1c8d3-4b7f-4e29-a6d0-2c9f5b8e1a74",
"state_name": "QA",
"count": 12,
"per_project": [
{
"project_id": "4af68566-94a4-4eb3-94aa-50dc9427067b",
"count": 9
},
{
"project_id": "d1e8a3c5-7f2b-4a96-8e04-3b6c9d1f5a27",
"count": 3
}
]
}
],
"invalid_targets": []
}
}The orphan gate
A chain replacement that removes states can strand work items in states the workflow no longer has. Before Plane writes anything, it counts the work items whose state isn't in the new chain, in every project and work item type whose pick points at this workflow. The count includes archived work items and leaves out drafts and work items in triage. A replacement that only adds or reorders states skips the gate.
If a stranded state is missing from state_mapping, or a mapping value isn't in the new chain, the request returns 409 workflow_state_orphans and changes nothing. The orphans member of the body carries the report:
orphans.orphans: one entry per stranded state, withstate_id,state_name, the totalcount, and aper_projectbreakdown ofproject_idandcount.orphans.invalid_targets: the mapping values that aren't in the new chain.
Build state_mapping from the report and resend the same body. Plane then replaces the chain and moves each stranded work item to its mapped state, with an activity entry on every work item it moves. Up to 500 work items per project and work item type move during the request. Above that, the move runs in the background, so a read right after the response can still show the old states.
Get usage first
Get workspace workflow usage lists the projects and work item types that resolve to the workflow, so you can see what a chain change affects before you send it.

