Post a comment on a work item. The comment is attached to the work item in the path and appears in its discussion immediately.
The author is taken from the credentials on the request and returned as actor_id — you cannot post a comment on behalf of another user.
Comments are not deduplicated
Nothing about a comment's body or external_id identifies it uniquely, so sending the same comment_html twice creates two comments. If your integration must post a comment at most once, send an external_id and look for it first with GET …/comments/?external_id=…&external_source=….
The comment body, as HTML — for example <p>Deployed the fix to staging.</p>. Plane derives the plain-text comment_stripped from it server-side, which is what search matches.
access:optionalstring
Visibility of the comment.
INTERNAL — visible to the project team
EXTERNAL — marked as visible outside the team, for example on a published project
Omit it to take the server's default. Change it later with a PATCH.
external_id:optionalstring
Your system's identifier for this comment, for sync and import correlation. Maximum 255 characters. Accepts null.
external_source:optionalstring
The system external_id came from, for example github or zendesk. Maximum 255 characters. Accepts null.
Comma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Comma-separated relations to embed alongside the ids: actor (the comment author).
Expansion is separate-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 is a 400.
?fields= and ?expand= are independent namespaces. Relation names are not valid ?fields= tokens (and vice versa), and an expanded object survives field filtering — ?fields=id,name&expand=state returns id, name and state. See Expanding relations.
Create a comment
Post a comment on a work item. The comment is attached to the work item in the path and appears in its discussion immediately.
The author is taken from the credentials on the request and returned as
actor_id— you cannot post a comment on behalf of another user.Comments are not deduplicated
Nothing about a comment's body or
external_ididentifies it uniquely, so sending the samecomment_htmltwice creates two comments. If your integration must post a comment at most once, send anexternal_idand look for it first withGET …/comments/?external_id=…&external_source=….Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs — in
https://app.plane.so/my-team/projects/, the slug ismy-team.project_id:requiredstring (uuid)The project the work item belongs to.
work_item_id:requiredstring (uuid)The work item to comment on.
Body Parameters
comment_html:requiredstringThe comment body, as HTML — for example
<p>Deployed the fix to staging.</p>. Plane derives the plain-textcomment_strippedfrom it server-side, which is what search matches.access:optionalstringVisibility of the comment.
INTERNAL— visible to the project teamEXTERNAL— marked as visible outside the team, for example on a published projectOmit it to take the server's default. Change it later with a
PATCH.external_id:optionalstringYour system's identifier for this comment, for sync and import correlation. Maximum 255 characters. Accepts
null.external_source:optionalstringThe system
external_idcame from, for examplegithuborzendesk. Maximum 255 characters. Acceptsnull.Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as
null, so absent means "not requested" andnullmeans "actually null".idalways comes back whether or not you name it.Pass
allfor every requestable field. An unknown name is a400that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.Requestable here:
access,actor_id,comment_html,comment_stripped,created_at,created_by_id,edited_at,external_id,external_source,id,work_item_id.See Sparse fields.
expand:optionalstringComma-separated relations to embed alongside the ids:
actor(the comment author).Expansion is separate-key:
?expand=statekeepsstate_idand adds astateobject next to it, so an id is never replaced by an object. An unknown value is a400.?fields=and?expand=are independent namespaces. Relation names are not valid?fields=tokens (and vice versa), and an expanded object survives field filtering —?fields=id,name&expand=statereturnsid,nameandstate. See Expanding relations.Scopes
projects.work_items.comments:writeErrors
400invalid_requestcomment_html, or anaccessvalue outside the enum.401unauthorized402payment_required403forbidden404not_found406not_acceptableAcceptheader asks for a representation the API can't produce.409conflict413payload_too_large415unsupported_media_typeContent-Typeisn't one this endpoint accepts.429rate_limitedRetry-Afterheader before retrying.