Skip to content

Update a module ​

PATCH/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/

Update a module in place — move it to in-progress, hand it to a new lead, push out its target date.

The update is partial: fields you omit are untouched. Omitting a field is not the same as sending null, which clears a nullable field.

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 the module belongs to.

pk:requiredstring (uuid)

The module to update.

Body Parameters ​

name:optionalstring

New display name, unique within the project. Maximum 255 characters. Renaming onto an existing module's name returns 409 conflict.

description:optionalstring

Plain-text summary of what the module covers.

status:optionalstring

Move the module to a different lifecycle position.

  • backlog — Captured, not yet committed to
  • planned — Committed to but not started
  • in-progress — Actively being worked on
  • paused — Started, then put on hold
  • completed — Delivered
  • cancelled — Dropped without delivering

Any value outside this list is a 400 invalid_request. Transitions are unrestricted — a completed module can be sent back to in-progress.

start_date:optionalstring (date)

Date the module is scheduled to begin, as YYYY-MM-DD. Send null to clear it.

target_date:optionalstring (date)

Date the module is expected to land, as YYYY-MM-DD. Send null to clear it. Must not be earlier than the module's start_date — including the start_date already stored when you only send target_date.

lead_id:optionalstring (uuid)

Reassign the module. Must be a member of this project — any other user id is rejected with a 400 naming lead_id. Send null to leave the module without a lead.

sort_order:optionalnumber

Ordering weight used when modules are listed. Lower values sort first.

logo_props:optionalany

Free-form JSON object holding the icon Plane renders for the module. The value you send replaces the stored object.

external_id:optionalstring

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

external_source:optionalstring

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

No PUT

v2 has no PUT. Sending one returns 405 method_not_allowed — use PATCH with only the fields you want to change.

member_ids, archived_at, and the audit fields are read-only. Sending them has no effect.

Response shaping ​

fields:optionalstring

Comma-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: archived_at, created_at, created_by_id, description, external_id, external_source, id, lead_id, logo_props, member_ids, name, sort_order, start_date, status, target_date.

See Sparse fields.

expand:optionalstring

Comma-separated relations to embed alongside the ids: lead (the module lead), members (the module members).

Expansion is separate-key: ?expand=state keeps state_id and adds a state object next to it, so an id is never replaced by an object. An unknown value is a 400.

?fields= and ?expand= are independent namespaces. Relation names are not valid ?fields= tokens (and vice versa), and an expanded object survives field filtering — ?fields=id,name&expand=state returns id, name and state. See Expanding relations.

Scopes ​

projects.modules:write

Errors ​

StatusCodeCause
400invalid_requestA status outside the enum, a lead_id who isn't a project member, or a target_date before start_date.
401unauthorizedMissing or invalid credentials.
402payment_requiredThe feature this endpoint belongs to isn't enabled on your plan, or is switched off.
403forbiddenYour role or token scope can't edit this module.
404not_foundNo such module, workspace, or project, or it's outside your tenant.
406not_acceptableThe Accept header asks for a representation the API can't produce.
409conflictAnother module in the project already uses this name.
413payload_too_largeThe request body is over the size limit.
415unsupported_media_typeThe Content-Type isn't one this endpoint accepts.
429rate_limitedThrottled. Wait for the interval in Retry-After and retry.
Update a module
bash
curl -X PATCH \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/modules/7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "paused",
  "target_date": "2026-03-13"
}'
Response200
json
{
  "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45",
  "name": "Billing revamp",
  "description": "Rework subscription billing end to end.",
  "status": "paused",
  "start_date": "2026-01-05",
  "target_date": "2026-03-13",
  "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"],
  "sort_order": 65535.0,
  "logo_props": {},
  "external_id": null,
  "external_source": null,
  "archived_at": null,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}
Response400
json
{
  "type": "invalid_request",
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "errors": [
    {
      "field": "lead_id",
      "code": "does_not_exist",
      "message": "Invalid pk \"b4d70c11-9e35-4a2f-8d6c-1f0ab3e97c52\" - object does not exist."
    }
  ]
}