Skip to content

Create a module ​

POST/api/v2/workspaces/{slug}/projects/{project_id}/modules/

Create a module in a project. Only name is required. You can set the other fields later with PATCH.

Module names must be unique within a project. Reusing one returns 409 conflict.

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

Body parameters ​

name:requiredstring

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

description:optionalstring

Plain-text summary of what the module covers.

status:optionalstring

Where the module sits in its lifecycle.

  • 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

Defaults to planned when omitted. A value outside this list returns 400 invalid_request.

start_date:optionalstring (date)

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

target_date:optionalstring (date)

Date the module is expected to land, as YYYY-MM-DD. Nullable, and must not be earlier than start_date.

lead_id:optionalstring (uuid)

The user accountable for the module. Must be a member of this project. Any other user id returns a 400 naming lead_id, and the user is not linked. Nullable.

sort_order:optionalnumber

Ordering weight used when modules are listed. Lower values sort first. In a project that already has modules, Plane positions the new module ahead of them on create, so send sort_order in a follow-up PATCH if you need a specific slot.

logo_props:optionalany

Free-form JSON object holding the icon Plane renders for the module.

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.

Members aren't set here

member_ids is read-only in v2, so a module is created with no members. Module membership is not yet writable through the v2 API.

Response shaping ​

fields:optionalstring

Comma-separated list of fields to return. Fields you don't request are left out of the response instead of coming back as null, so a missing key means you didn't ask for it and null means the value is null. id is always included, whether or not you name it.

Pass all to get every requestable field. An unknown field name returns 400 with the list of valid names and the closest match, so a typo is caught instead of being silently ignored.

Requestable here: archived_at, backlog_issues, cancelled_issues, completed_issues, created_at, created_by_id, description, description_html, external_id, external_source, id, lead_id, logo_props, member_ids, name, sort_order, start_date, started_issues, status, target_date, total_issues, unstarted_issues, updated_at, updated_by_id.

See Sparse fields.

expand:optionalstring

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

Expanded objects get their own 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 returns 400.

?fields= and ?expand= take separate sets of names. Relation names aren't valid in ?fields=, field names aren't valid in ?expand=, and field filtering never removes an expanded object: ?fields=id,name&expand=state returns id, name and state. See Expanding relations.

Scopes ​

projects.modules:write

Errors ​

StatusCodeCause
400invalid_requestMissing name, a 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 create modules in this project.
404not_foundNo such workspace or project, or it's outside your tenant.
406not_acceptableThe Accept header asks for a representation the API can't produce.
409conflictA module with this name already exists in the project.
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.
Create a module
bash
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/modules/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Billing revamp",
  "description": "Rework subscription billing end to end.",
  "status": "in-progress",
  "start_date": "2026-01-05",
  "target_date": "2026-02-27",
  "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}'
Response201
json
{
  "id": "7c1f3d90-2a64-4e58-9b0d-3fa1c7e28b45",
  "name": "Billing revamp",
  "description": "Rework subscription billing end to end.",
  "description_html": null,
  "start_date": "2026-01-05",
  "target_date": "2026-02-27",
  "status": "in-progress",
  "lead_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "member_ids": [],
  "sort_order": 65535.0,
  "external_id": null,
  "external_source": null,
  "logo_props": {},
  "total_issues": 0,
  "backlog_issues": 0,
  "unstarted_issues": 0,
  "started_issues": 0,
  "completed_issues": 0,
  "cancelled_issues": 0,
  "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
}
Response409
json
{
  "type": "conflict",
  "code": "conflict",
  "detail": "A module with this name already exists in the project."
}