Get a work item type schema
Ask the API what a work item of this type accepts. The response describes the standard fields every work item has, with the project's actual states, priorities, and estimate points inlined as valid options. It also describes the custom properties attached to this type, including which ones are required and what values they allow.
A client that is about to create a work item calls this endpoint first, then builds its request body from the answer instead of hardcoding a field list. This matters most for two kinds of client:
- Integrations, which must adapt when an admin adds a required property to a type. If you read the schema each time, a new required field shows up as a field to fill instead of a
400in production. - AI agents and generated tooling, which need a machine-readable contract. You can pass
fieldsandcustom_fieldsstraight to a function-calling schema: every entry carries a type, a required flag, and its allowed options.
Reads are unaffected by work item type mode. The schema resolves the type's properties whether the project or the workspace owns them.
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 type belongs to. The schema is project-specific: states, labels, members, and estimate points all come from this project.
pk:requiredstring (uuid)The id of the work item type whose schema you want.
Query parameters
include:optionalstringComma-separated extra option lists to inline. Two values are recognized:
members: inlines the project's active members as the options forassignee_ids, and for any custom property that is a user relationlabels: inlines the project's labels as the options forlabel_ids
If you omit it, those fields still appear, but without an option list. These lists can be large in a big workspace, so ask for them only when you are rendering a picker, for example with ?include=members,labels.
Response fields
type_id:requiredstring (uuid)The type this schema describes. Send it as type_id on the work item you create. Nullable.
type_name:requiredstringThe type's display name, for example Bug. Nullable.
type_description:requiredstringThe type's description. A human or an agent reads this text to decide whether this is the right type for the work, so fill it in on create. Nullable.
type_logo_props:requiredanyThe icon and background color rendered next to the type. Nullable.
fields:requiredanyThe standard work item fields, keyed by the body parameter name you would send: name, description_html, priority, state_id, assignee_ids, label_ids, start_date, target_date, parent_id. Each entry carries a type, a required flag, and where it applies an is_multi flag, a default, a max_length, a format, or an options array.
state_id and priority always arrive with their options inlined, because those are the two fields a client cannot guess. estimate_point_id appears only when the project has an estimate system configured.
custom_fields:requiredanyThe custom properties attached to this type, keyed by property name. Each entry carries the property id, its type, name, display_name, description, a required flag, and an is_multi flag.
OPTIONproperties also carry anoptionsarray of{ id, name, logo_props }, narrowed to the options this type allows.RELATIONproperties also carry arelation_type.
Empty when the type has no properties attached, or when custom properties are not available for the workspace.
Scopes
projects.work_item_types:read
Errors
| Status | Code | Cause |
|---|---|---|
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. |
403 | forbidden | Your role or token scope can't read this project's work item types. |
404 | not_found | No such type, project, or workspace, or the type belongs to another project. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X GET \
"https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/schema/?include=members,labels" \
-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/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/schema/",
headers={"X-Api-Key": "your-api-key"},
params={"include": "members,labels"},
)
schema = response.json()
required = [name for name, field in schema["custom_fields"].items() if field["required"]]
print(schema["type_name"], "requires:", required)const params = new URLSearchParams({ include: "members,labels" });
const response = await fetch(
`https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-types/d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240/schema/?${params}`,
{
headers: {
"X-Api-Key": "your-api-key",
},
},
);
const schema = await response.json();
const required = Object.entries(schema.custom_fields)
.filter(([, field]) => field.required)
.map(([name]) => name);{
"type_id": "d1e7a4c9-2b58-4f36-9a0e-7c3b5d81f240",
"type_name": "Bug",
"type_description": "Something is broken and needs a fix",
"type_logo_props": {
"in_use": "icon",
"icon": {
"name": "AlertCircle",
"background_color": "#EF5974"
}
},
"fields": {
"name": {
"type": "string",
"required": true,
"max_length": 255
},
"description_html": {
"type": "string",
"required": false
},
"priority": {
"type": "option",
"required": false,
"default": "none",
"options": [
{ "value": "urgent", "label": "Urgent" },
{ "value": "high", "label": "High" },
{ "value": "medium", "label": "Medium" },
{ "value": "low", "label": "Low" },
{ "value": "none", "label": "None" }
]
},
"state_id": {
"type": "uuid",
"required": false,
"options": [
{
"id": "2c4d16f8-9b3e-4a52-8d71-1f0e6c9a5b48",
"name": "Backlog",
"color": "#8b8d98",
"group": "backlog"
},
{
"id": "f960d3c2-8524-4a41-b8eb-055ce4be2a7f",
"name": "In Progress",
"color": "#3f76ff",
"group": "started"
},
{
"id": "7b1e9d40-3c86-4f2a-9a5d-8e2b0c47d613",
"name": "Done",
"color": "#26a05f",
"group": "completed"
}
]
},
"assignee_ids": {
"type": "uuid",
"is_multi": true,
"required": false,
"options": [
{
"id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"display_name": "rin",
"email": "rin@my-team.dev"
}
]
},
"label_ids": {
"type": "uuid",
"is_multi": true,
"required": false,
"options": [
{
"id": "5e0c7b93-1a24-4d68-8f31-9b7e2c05a4d7",
"name": "regression",
"color": "#f5a623"
}
]
},
"start_date": {
"type": "date",
"required": false,
"format": "YYYY-MM-DD"
},
"target_date": {
"type": "date",
"required": false,
"format": "YYYY-MM-DD"
},
"parent_id": {
"type": "uuid",
"required": false
}
},
"custom_fields": {
"severity": {
"id": "a8b31d67-5e42-4c09-9f78-2d6b41e0c395",
"type": "OPTION",
"name": "severity",
"display_name": "Severity",
"description": "How badly this breaks the product",
"required": true,
"is_multi": false,
"options": [
{
"id": "3f8c2a71-6d90-4e15-b243-0c9e7a5b16f8",
"name": "S1",
"logo_props": {}
},
{
"id": "b47e0d29-8153-4a6c-9e70-2f1d5c83a904",
"name": "S2",
"logo_props": {}
}
]
},
"found_by": {
"id": "c0a95f38-7b1e-4d62-8305-6e4a9b27fd51",
"type": "RELATION",
"name": "found_by",
"display_name": "Found by",
"description": "",
"required": false,
"is_multi": false,
"relation_type": "USER",
"options": [
{
"id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
"display_name": "rin",
"email": "rin@my-team.dev"
}
]
}
}
}How to use the schema
The response tells you how to build the next request. A client that reads it correctly needs no hardcoded knowledge of the project's configuration.
- Pick the type. Read
type_nameandtype_descriptionto confirm this is the right type, and carrytype_idinto the work item body. - Fill the standard fields from
fields. Anything with"required": truemust be present. Where an entry hasoptions, send one of thoseidvalues. A state id from another project returns400instead of being linked. - Fill the custom properties from
custom_fields. Send them undercustom_fieldson the work item, keyed by the same property name. Entries with"required": truemust be present on create. - Respect
is_multi. A field with"is_multi": truetakes an array; one without takes a single value.
Re-read the schema, don't cache it forever
Admins add and retire properties. A property that became required since your last sync is the most common cause of a surprise 400 on work item create. Reading the schema at the start of a sync run costs one request and prevents that failure.
Property types
custom_fields entries carry the property's type. The possible values are TEXT, DATETIME, DECIMAL, BOOLEAN, OPTION, RELATION, URL, EMAIL, FILE, and FORMULA.
A RELATION property also carries relation_type, one of ISSUE, USER, RELEASE, or RICH_TEXT, which tells you what kind of id its value refers to.
Options are narrowed per type
The options list on an OPTION property is what this type allows, which can be a subset of every option defined on the property. Read the options from the schema rather than from the property options endpoint when you want the values valid for this specific type.
Related
- Attach a property to a type: adds an entry to
custom_fields - Create a work item property: defines the property itself
- Create a work item: builds the work item from the schema

