Skip to content

Modules overview ​

A module groups work items into a single deliverable inside one project — a launch, a migration, a redesign. Modules belong to exactly one project and are never shared across projects.

Where a cycle answers "what are we doing this sprint", a module answers "what does this piece of work consist of". A work item can sit in a module and a cycle at the same time.

Learn more about modules

The module object ​

Attributes ​

  • id string (uuid)

    Unique identifier for the module.

  • name string

    Display name, unique within the project. Maximum 255 characters.

  • description string

    Plain-text summary of what the module covers.

  • description_html string or null

    The module's rich-text description, as HTML, or null. Read-only through the API, which writes only description. Omitted from list rows unless ?fields= names it.

  • status string

    Where the module sits in its lifecycle. One of backlog, planned, in-progress, paused, completed, or cancelled.

  • start_date string (date)

    Date the module is scheduled to begin, as YYYY-MM-DD, or null.

  • target_date string (date)

    Date the module is expected to land, as YYYY-MM-DD, or null.

  • lead_id string (uuid)

    The project member accountable for the module, or null.

  • member_ids array of string

    Project members assigned to the module. Read-only in v2 — module membership is not yet writable through the v2 API.

  • sort_order number

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

  • logo_props any

    Free-form JSON object holding the icon Plane renders for the module. {} when no icon is set.

  • external_id , external_source string

    Correlation fields for sync and import. Together they let you map a module to a record in another system and find it again later.

  • total_issues integer

    The number of live work items in the module, across every state group. Omitted from list rows unless ?fields= names it. See Progress counts.

  • backlog_issues, unstarted_issues, started_issues, completed_issues, cancelled_issues integer

    The number of live work items in the module whose state belongs to that group. Omitted from list rows unless ?fields= names them.

  • archived_at string (date-time) or null

    When the module was archived, or null for an active module. Set by Archive a module and cleared by Unarchive a module.

  • created_at string (date-time)

    When the module was created.

  • updated_at string (date-time)

    When the module was last updated.

  • created_by_id string (uuid)

    The user who created the module.

  • updated_by_id string (uuid) or null

    The user who last updated the module. null until someone updates it.

Expand the lead and members

lead_id and member_ids come back as ids. Add ?expand=lead,members to embed the user objects next to them, or resolve the ids against project members. See Expanding relations.

Response200
json
{
  "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45",
  "name": "Billing revamp",
  "description": "Rework subscription billing end to end.",
  "description_html": "<p>Rework subscription billing end to end.</p>",
  "start_date": "2026-01-05",
  "target_date": "2026-02-27",
  "status": "in-progress",
  "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "member_ids": ["16c61a3a-512a-48ac-b0be-b6b46fe6f430", "9d3e1f27-8b4c-4a06-95f1-2c7ea45b0d18"],
  "sort_order": 65535.0,
  "external_id": null,
  "external_source": null,
  "logo_props": {},
  "total_issues": 24,
  "backlog_issues": 3,
  "unstarted_issues": 5,
  "started_issues": 6,
  "completed_issues": 9,
  "cancelled_issues": 1,
  "archived_at": null,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "updated_at": "2026-01-14T09:22:41.478363Z",
  "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "updated_by_id": null
}

Endpoints ​

MethodPathDescription
GET/api/v2/workspaces/{slug}/projects/{project_id}/modules/List modules
GET/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/Get a module
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/Create a module
PATCH/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/Update a module
DELETE/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/Delete a module
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/upsert/Upsert a module
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/bulk-create/Bulk create modules
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/bulk-update/Bulk update modules
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/bulk-delete/Bulk delete modules
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/work-items/Add or remove module work items
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/archive/Archive a module
POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/{pk}/unarchive/Unarchive a module

Status ​

status is the module's lifecycle position and is validated on both writes and the status filter — a value outside the enum is a 400 invalid_request, never a silently ignored write or an empty list.

ValueMeaning
backlogCaptured, not yet committed to
plannedCommitted to but not started
in-progressActively being worked on
pausedStarted, then put on hold
completedDelivered
cancelledDropped without delivering

A module created without a status starts as planned.

Lead and members ​

lead_id must be a member of the module's project. Passing a user who isn't a project member is rejected with 400 invalid_request naming lead_id — the link is never made silently.

member_ids is read-only. Reads return the module's current members, but there is no v2 write path for adding or removing them yet.

Dates ​

start_date and target_date are plain dates (YYYY-MM-DD), and either can be null. A target_date earlier than start_date is rejected with 400 invalid_request.

Names are unique per project ​

Two modules in the same project cannot share a name. A duplicate on create — or on a rename via PATCH — returns 409 conflict.

Progress counts ​

Module reads carry six counts of the work items in the module: total_issues, backlog_issues, unstarted_issues, started_issues, completed_issues, and cancelled_issues.

Only live work items count. A work item is left out when it has been removed from the module, archived, or deleted, or when it is still a draft. total_issues counts every live work item, and each group count covers one state group. A work item in a triage state counts toward total_issues and toward none of the five groups, so the five can add up to less than the total.

Single-object responses (get, create, update, and upsert) include the counts and description_html. List rows leave them out. Name them in ?fields=, or pass ?fields=all, to get them on list rows:

bash
GET .../modules/?fields=id,name,status,total_issues,completed_issues

Archiving ​

Archive a module sets archived_at, and Unarchive a module clears it. Only a module whose status is completed or cancelled can be archived. Any other status returns 409 conflict.

Archived modules drop out of List modules by default. Pass ?is_archived=true to list only archived modules, or ?include_archived=true to list archived and active modules together. Get a module returns an archived module by id either way.

Module work items ​

Add work items to a module or remove them with Add or remove module work items, and list a module's work items by filtering List work items by module_id.

Changed from v1 ​

  • lead is now lead_id, members is now member_ids, created_by is now created_by_id, and updated_by is now updated_by_id.
  • member_ids is read-only.
  • Reads no longer return project, workspace, view_props, or description_text. description_html is read-only.
  • description_html and the progress counts are omitted from list rows unless ?fields= names them. v1 returns them on every row.
  • Archived modules are listed with ?is_archived=true on the regular list instead of a separate archived-modules/ route. Unarchive is POST …/modules/{pk}/unarchive/ instead of DELETE …/archived-modules/{pk}/unarchive/.
  • Archiving a module that is not completed or cancelled returns 409 conflict. v1 returns 400.
  • PUT is gone — updates are PATCH and partial.

See Migrating from v1 for the full list.