Skip to content

Create a work item property ​

POST/api/v2/workspaces/{slug}/projects/{project_id}/work-item-properties/

Define a new custom field in a project. You choose a label and a property_type, and Plane returns the property definition, including the derived name slug you can use as a key in your own storage.

Creating a property does not put it on any work item. The definition exists, but nothing renders it until you attach it to a work item type with Attach a type property.

You write display_name, not name. name is the read-side slug Plane derives, and it is not a body 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 to define the property in. Project-level properties belong only to this project.

Body parameters ​

display_name:requiredstring

The human-readable label for the field, for example Severity. Maximum 255 characters. Plane derives the read-only name slug from this value and returns both.

property_type:requiredstring

What kind of data the field holds. The rest of the property's configuration depends on it.

  • TEXT: free-form text
  • DATETIME: a point in time, written as an ISO 8601 timestamp
  • DECIMAL: a number, carried as a string in the value array
  • BOOLEAN: a yes/no flag
  • OPTION: a choice from a fixed set you define
  • RELATION: a reference to another record. Pair it with relation_type.
  • URL: a link
  • EMAIL: an email address
  • FILE: an uploaded file
  • FORMULA: a value Plane computes, not one a person enters

The full reference, including what each type means for the value you send on a work item, is on the properties overview.

relation_type:optionalstring

What a RELATION property points at. Only meaningful when property_type is RELATION. Leave it out otherwise.

  • ISSUE: a work item
  • USER: a member
  • RELEASE: a release
  • RICH_TEXT: rich text content
description:optionalstring

Free-form explanation of what the field is for. People read it as helper text when deciding what to enter.

options:optionalarray of object

Write-only. Define the choices for an OPTION property inline, in the same request that creates it, instead of a second round-trip per choice.

Each entry is a property option object. name is required, and description, is_default, external_id, and external_source are accepted. See Create a property option for the full field list.

The response does not return options in the shape you sent. It returns the resolved options array, with each choice's generated id and sort_order.

is_multi:optionalboolean

Allow more than one value on a work item, for example a multi-select list of severities, a set of reviewers, or several linked work items. Leave it off for a single-valued field.

is_required:optionalboolean

Require a value. Give a required property a default_value so existing flows have something to fall back on.

is_active:optionalboolean

Whether the property is offered. Create it inactive if you want to define the field and its options now but roll it out later.

default_value:optionalarray of string

The value applied when none is supplied. Always an array, even for a single-valued property: a DECIMAL field that defaults to 3 is sent as ["3"], not 3. A property with no default sends [] or omits the field.

For an OPTION property, mark the default choice with is_default on the option itself instead of repeating it here.

settings:optionalany

Free-form object holding type-specific configuration. What belongs in it depends on property_type, so there is no single schema. Send the whole object instead of assuming keys.

validation_rules:optionalany

Free-form object holding type-specific validation constraints. As with settings, the shape depends on property_type.

external_id:optionalstring

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

external_source:optionalstring

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

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: created_at, default_value, description, display_name, external_id, external_source, id, is_active, is_multi, is_required, logo_props, name, options, property_type, relation_type, settings, updated_at, updated_by_id, validation_rules.

See Sparse fields.

Scopes ​

projects.work_item_properties:write

Errors ​

StatusCodeCause
400invalid_requestMissing display_name or property_type, or an enum value outside the list.
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 properties.
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.
409conflictThis workspace manages work item types at the workspace level. Create the property on the workspace endpoint instead.
413payload_too_largeThe request body is over the size limit.
415unsupported_media_typeThe Content-Type isn't one this endpoint accepts.
429rate_limitedThrottled. Honor the Retry-After header before retrying.

A 409 means wrong surface, not missing permission

If the workspace manages work item types at the workspace level, this project endpoint returns 409 work_item_types_managed_at_workspace. Nothing is broken and no permission is missing. Create the property through Create a workspace work item property instead. See Work item type modes for how to detect which mode you are in.

Create a work item property
bash
curl -X POST \
  "https://api.plane.so/api/v2/workspaces/my-team/projects/4af68566-94a4-4eb3-94aa-50dc9427067b/work-item-properties/" \
  -H "X-Api-Key: $PLANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "display_name": "Severity",
  "description": "How badly this affects customers",
  "property_type": "OPTION",
  "is_multi": false,
  "is_required": true,
  "is_active": true,
  "options": [
    { "name": "Critical", "description": "Production is down" },
    { "name": "Major", "description": "A core workflow is broken", "is_default": true },
    { "name": "Minor", "description": "Cosmetic or low impact" }
  ]
}'
Response201
json
{
  "id": "9d2f0b74-6a51-4c8e-b3d7-2f1a8c05e964",
  "name": "severity",
  "display_name": "Severity",
  "description": "How badly this affects customers",
  "property_type": "OPTION",
  "relation_type": null,
  "is_multi": false,
  "is_required": true,
  "is_active": true,
  "default_value": [],
  "options": [
    {
      "id": "3e7a5c19-42b8-4d06-9f3e-7c1b8a0d2456",
      "name": "Critical",
      "description": "Production is down",
      "parent_id": null,
      "is_default": false,
      "is_active": true,
      "sort_order": 15000,
      "logo_props": {},
      "external_id": null,
      "external_source": null,
      "updated_at": "2026-01-14T09:22:41.478363Z",
      "updated_by_id": null
    },
    {
      "id": "b6c04f83-1d29-4e57-8a3b-90e2f5c7d418",
      "name": "Major",
      "description": "A core workflow is broken",
      "parent_id": null,
      "is_default": true,
      "is_active": true,
      "sort_order": 25000,
      "logo_props": {},
      "external_id": null,
      "external_source": null,
      "updated_at": "2026-01-14T09:22:41.478363Z",
      "updated_by_id": null
    },
    {
      "id": "7f52d3a8-0e14-4c69-b28d-a1f6e903c5b7",
      "name": "Minor",
      "description": "Cosmetic or low impact",
      "parent_id": null,
      "is_default": false,
      "is_active": true,
      "sort_order": 35000,
      "logo_props": {},
      "external_id": null,
      "external_source": null,
      "updated_at": "2026-01-14T09:22:41.478363Z",
      "updated_by_id": null
    }
  ],
  "settings": {},
  "validation_rules": {},
  "logo_props": {},
  "external_id": null,
  "external_source": null,
  "created_at": "2026-01-14T09:22:41.478363Z",
  "updated_at": "2026-01-14T09:22:41.478363Z",
  "updated_by_id": null
}
Response409
json
{
  "type": "conflict",
  "code": "work_item_types_managed_at_workspace",
  "detail": "Work item types are managed at the workspace level for this workspace."
}
Response400
json
{
  "type": "invalid_request",
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "errors": [
    {
      "field": "property_type",
      "code": "invalid_choice",
      "message": "\"SELECT\" is not a valid choice."
    }
  ]
}

Creating a relation property ​

A RELATION property needs relation_type to say what it points at. This one lets a work item name several reviewers:

json
{
  "display_name": "Reviewer",
  "description": "Who signs this off",
  "property_type": "RELATION",
  "relation_type": "USER",
  "is_multi": true
}

relation_type stays null on every non-RELATION property.

After you create ​

  1. Attach it to a type with Attach a type property. That makes the field appear on work items. One property can be attached to several types.
  2. Add or change options later. Inline options sets up the initial choices, and Property options handles changes over the property's life.