A comment is a note posted on a single work item. Comments are a sub-resource of a work item, not a project-level collection: every route is mounted under the work item, so a comment can only be read or written through the work item it belongs to.
That means the collection routes carry three path parameters (slug, project_id, work_item_id) and the detail routes carry four (plus pk). There is no workspace-wide or project-wide comment feed — to gather comments across work items, list them per work item.
The work item this comment is attached to. Always matches the work_item_id in the request path.
comment_htmlstring
The comment body, as HTML. This is the field you write.
comment_strippedstring
The plain-text version of comment_html, derived server-side. It is what ?search= matches, so markup never affects a search hit. Read-only — you never send it.
accessstring
Visibility of the comment. One of INTERNAL or EXTERNAL. INTERNAL keeps the comment inside the project team; EXTERNAL marks it as visible outside the team, for example on a published project.
actor_idstring (uuid) or null
The author of the comment — the principal that posted it. Set server-side from the authenticated caller, so you cannot post a comment on someone else's behalf.
edited_atstring (date-time) or null
When the comment was last updated through a PATCH. null on a comment that has never been edited, which is how clients render an "edited" marker.
external_id , external_sourcestring or null
Correlation fields for sync and import. Together they let you map a comment to a record in another system and find it again with ?external_id= and ?external_source=.
created_atstring (date-time)
When the comment was created.
created_by_idstring (uuid) or null
The user record that created the comment. For comments posted by a person this is the same user as actor_id; actor_id is the domain field that Plane renders as the comment author.
Write comment_html, read comment_stripped
comment_html is the only body field you can set. Plane derives comment_stripped from it on every write, so the two never drift apart.
Response200
json
{ "id": "c1f7a3d9-2b64-4f80-9c1a-3d5e8b2a6c47", "work_item_id": "8f4c2b1e-0d3a-4f7b-9c21-6e5a8b7d4f13", "comment_html": "<p>Deployed the fix to staging. Please re-test.</p>", "comment_stripped": "Deployed the fix to staging. Please re-test.", "access": "INTERNAL", "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430", "external_id": null, "external_source": null, "edited_at": null, "created_at": "2026-01-14T09:22:41.478363Z", "created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"}
Nothing about a comment's body or external_id identifies it uniquely, so posting 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=… before creating.
Writes can still fail with 409 conflict, so branch on the code in the problem response instead of assuming a create always succeeds.
access is set per comment and can be changed later with a PATCH. Filter a list down to one visibility with ?access=INTERNAL or ?access=EXTERNAL when you need only the comments a particular audience can see.
Comment visibility is inherited from the work item in the path — there is no per-comment read permission. A pk that belongs to a different work item is not visible on this route and returns 404 resource_not_found, the same as a comment id that does not exist. Existence is never leaked.
Work item comments overview
A comment is a note posted on a single work item. Comments are a sub-resource of a work item, not a project-level collection: every route is mounted under the work item, so a comment can only be read or written through the work item it belongs to.
That means the collection routes carry three path parameters (
slug,project_id,work_item_id) and the detail routes carry four (pluspk). There is no workspace-wide or project-wide comment feed — to gather comments across work items, list them per work item.Learn more about work item comments
The comment object
Attributes
idstring (uuid)Unique identifier for the comment.
work_item_idstring (uuid)The work item this comment is attached to. Always matches the
work_item_idin the request path.comment_htmlstringThe comment body, as HTML. This is the field you write.
comment_strippedstringThe plain-text version of
comment_html, derived server-side. It is what?search=matches, so markup never affects a search hit. Read-only — you never send it.accessstringVisibility of the comment. One of
INTERNALorEXTERNAL.INTERNALkeeps the comment inside the project team;EXTERNALmarks it as visible outside the team, for example on a published project.actor_idstring (uuid) or nullThe author of the comment — the principal that posted it. Set server-side from the authenticated caller, so you cannot post a comment on someone else's behalf.
edited_atstring (date-time) or nullWhen the comment was last updated through a
PATCH.nullon a comment that has never been edited, which is how clients render an "edited" marker.external_id,external_sourcestring or nullCorrelation fields for sync and import. Together they let you map a comment to a record in another system and find it again with
?external_id=and?external_source=.created_atstring (date-time)When the comment was created.
created_by_idstring (uuid) or nullThe user record that created the comment. For comments posted by a person this is the same user as
actor_id;actor_idis the domain field that Plane renders as the comment author.Write
comment_html, readcomment_strippedcomment_htmlis the only body field you can set. Plane derivescomment_strippedfrom it on every write, so the two never drift apart.Endpoints
GET/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/POST/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/GET/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/PATCH/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/DELETE/api/v2/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/{pk}/Comments are not deduplicated
The API will not collapse duplicates for you
Nothing about a comment's body or
external_ididentifies it uniquely, so posting 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=…before creating.Writes can still fail with
409 conflict, so branch on thecodein the problem response instead of assuming a create always succeeds.Access and visibility
accessis set per comment and can be changed later with aPATCH. Filter a list down to one visibility with?access=INTERNALor?access=EXTERNALwhen you need only the comments a particular audience can see.Reads follow the parent work item
Comment visibility is inherited from the work item in the path — there is no per-comment read permission. A
pkthat belongs to a different work item is not visible on this route and returns404 resource_not_found, the same as a comment id that does not exist. Existence is never leaked.Changed from v1
/work-items/{work_item_id}/comments/and the parent id is reported aswork_item_idinstead ofissue.actoris nowactor_id, andcreated_byis nowcreated_by_id.updated_at,updated_by,deleted_at,project,workspace,comment_json,attachments, oris_member.edited_atis stamped by Plane on everyPATCH.See Migrating from v1 for the full list.