List type properties
Return the custom properties currently attached to one work item type, as a paginated list. This is the call that answers "what fields does a Bug have?" — reach for it before rendering a form, validating an import, or deciding which property values to send when you create a work item.
Each entry is the full property definition, not a link stub, so labels, input types, and option lists all arrive in this one response. Properties that exist in the project but are not attached to this type are not returned; for the project-wide inventory use List work item properties.
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.
project_id:requiredstring (uuid)The project the work item type belongs to.
type_id:requiredstring (uuid)The work item type whose properties you want to list.
Query Parameters
There are no filters on this endpoint — the attachment itself is the filter. What you can control is ordering and page size.
Neither order_by nor paginate is validated — check your spelling, because both fail silently. A value outside the lists below is not rejected: an unrecognized order_by falls back to the default ordering, and anything other than paginate=cursor uses offset pagination.
Ordering
order_by:optionalstringField to sort by. Prefix with - for descending.
sort_order,-sort_order— the order the properties are shown in, and the defaultcreated_at,-created_at— when each property was createdid,-id
Order by sort_order when you are rendering the type's form to a user; it is the order Plane itself uses. Note that sort_order is not part of the returned property object — sort by created_at or id if you need the sort key to be visible in the payload.
Pagination
per_page:optionalintegerPage size. Defaults to 50, maximum 200. Most types carry a handful of properties, so one page is usually the whole set.
offset:optionalintegerNumber of rows to skip from the start of the result set. Maximum 10000. Read the next value from the response rather than computing offsets yourself.
paginate:optionalstringSet to cursor to opt into the COUNT-free keyset envelope, which returns next_cursor and has_more instead of next and total_count. Pair it with order_by=created_at or order_by=id — the default sort_order is not a unique key, so it is not cursor-eligible. Omit the parameter for the default offset envelope.
count:optionalbooleanDefaults to true. Set to false to skip the COUNT(*) behind total_count; the field is then omitted from the response.
Scopes
projects.work_item_types:read
Errors
| Status | Code | Cause |
|---|---|---|
401 | unauthorized | Missing or invalid credentials. |
403 | forbidden | Your role or token scope can't read this project's work item types. |
404 | resource_not_found | No such workspace, project, or type — or it's outside your tenant. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
Reads work in either mode
Listing is not mode-gated. A project surfaces the properties on its types whether the workspace manages types at the project level or the workspace level — only the writes on this path (attach and detach) care. See Work item type modes.
curl -X GET \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/9d3c7f21-6b48-4e0a-8f52-2c1d7a904e66/properties/?order_by=sort_order&per_page=50" \
-H "X-Api-Key: $PLANE_API_KEY"import requests
response = requests.get(
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/9d3c7f21-6b48-4e0a-8f52-2c1d7a904e66/properties/",
headers={"X-Api-Key": "your-api-key"},
params={"order_by": "sort_order", "per_page": 50},
)
print(response.json())const params = new URLSearchParams({ order_by: "sort_order", per_page: "50" });
const response = await fetch(
`https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/9d3c7f21-6b48-4e0a-8f52-2c1d7a904e66/properties/?${params}`,
{
headers: {
"X-Api-Key": "your-api-key",
},
}
);
const data = await response.json();{
"data": [
{
"id": "c1f0a2d4-73b9-4e6c-8a15-9d0e4b3f7c28",
"name": "severity",
"display_name": "Severity",
"description": "How badly this bug affects users",
"property_type": "OPTION",
"relation_type": null,
"is_required": true,
"is_multi": false,
"is_active": true,
"default_value": [],
"settings": {},
"validation_rules": {},
"logo_props": {},
"external_id": null,
"external_source": null,
"created_at": "2026-01-14T09:22:41.478363Z",
"options": [
{
"id": "3b6f1c88-0d24-4a97-9f53-71e8c2b4a069",
"name": "Critical",
"description": "Data loss or full outage",
"is_default": false,
"sort_order": 10000,
"external_id": null,
"external_source": null
},
{
"id": "7e2a9046-5c31-4d8b-a4f6-0b95e13d72c8",
"name": "Major",
"description": "Core workflow blocked",
"is_default": true,
"sort_order": 20000,
"external_id": null,
"external_source": null
}
]
},
{
"id": "5e8b3417-2a6d-4c91-b0f7-8d2e14a6c395",
"name": "root-cause",
"display_name": "Root cause",
"description": "What actually broke",
"property_type": "TEXT",
"relation_type": null,
"is_required": false,
"is_multi": false,
"is_active": true,
"default_value": [],
"settings": {},
"validation_rules": {},
"logo_props": {},
"external_id": null,
"external_source": null,
"created_at": "2026-01-14T09:24:03.201884Z",
"options": []
}
],
"next": null,
"previous": null,
"total_count": 2,
"pagination": {
"style": "offset"
}
}{
"data": [
{
"id": "5e8b3417-2a6d-4c91-b0f7-8d2e14a6c395",
"name": "root-cause",
"display_name": "Root cause",
"description": "What actually broke",
"property_type": "TEXT",
"relation_type": null,
"is_required": false,
"is_multi": false,
"is_active": true,
"default_value": [],
"settings": {},
"validation_rules": {},
"logo_props": {},
"external_id": null,
"external_source": null,
"created_at": "2026-01-14T09:24:03.201884Z",
"options": []
}
],
"next_cursor": "b3A9MTcx",
"has_more": true,
"pagination": {
"style": "cursor"
}
}Branch on property_type, not on the label
display_name is what a human renamed the field to this week. property_type, is_multi, and is_required are what tell your code whether to render a date picker or a multi-select and whether a value is mandatory. Key your integration on id and property_type; treat display_name as text for humans.

