> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Customize comment actions

Add, remove, and reorder the actions on a comment with commentActions and CommentActionBar.

The actions on a comment are an array. `commentActions` builds the package's default list, `CommentActionBar` renders it, and both are exported, so adding, removing, and reordering entries are array operations rather than props. This guide covers each of those, and where an action's flow lives.

Prerequisites:

- A comment layout that renders its own `CommentActionBar`. The default `Comment` builds this list internally. See [Build a custom comment](https://www.sanity.io/docs/collaboration/build-a-custom-comment).

## Build the default list

Every snippet on this page imports from one list:

```tsx
import {SDKCommentsProvider} from '@sanity/collaboration/sdk'
import {
  AddReactionButton,
  CommentActionBar,
  commentActions,
  CommentsProvider,
  DeleteCommentDialog,
  useCommentsContext,
} from '@sanity/collaboration/ui'
import {WarningOutlineIcon} from '@sanity/icons/WarningOutline'
import {Flex} from '@sanity/ui'
import {useState} from 'react'
```

```typescript
const {resolveCapabilities} = useCommentsContext()
const capabilities = resolveCapabilities(comment)

const actions = commentActions({
  comment,
  capabilities,
  onStatusChange,
  onEdit,
  onDelete,
  onCopyLink,
  onRetry,
})
```

`comment` is the comment your card renders, from its props. `onStatusChange`, `onEdit`, `onDelete`, `onCopyLink`, and `onRetry` are your card's own handlers. Pass the ones the card supports.

`comment` is typed as the fields the list branches on, so a whole `CommentData` fits and so does an object carrying only those. `capabilities` comes from `useCommentsContext().resolveCapabilities(comment)`, which applies the provider's policy for you. Unlike `commentActions`, `resolveCapabilities` needs the whole `CommentData`.

It returns resolve or reopen, retry, edit, copy link, and delete, in that order. An entry appears only when its callback was passed, and is disabled when the matching capability resolved to false: a missing callback renders nothing, and a forbidden one renders grayed out. Two entries have a second condition: resolve or reopen appears only on a thread parent, a comment with no `parentCommentId`, and retry appears only on a comment whose `state.type` is `'createError'`.

The order is part of the contract, because it decides which entries `CommentActionBar` promotes to buttons. How many it promotes is `maxVisible`, covered in [CommentActionBar](https://collaboration-platform.sanity.dev/?path=/docs/primitives-commentactionbar--docs).

## Add an action

An entry is a `CommentAction`: a name, an action, and optionally an icon, a disabled flag, and a critical tone. The full shape is in [Helpers](https://collaboration-platform.sanity.dev/?path=/docs/helpers--docs#commentactions).

```tsx
const [flagged, setFlagged] = useState(false)

const actions = [
  ...commentActions({comment, capabilities, onEdit, onDelete}),
  {
    name: 'Mark as spam',
    icon: WarningOutlineIcon,
    action: () => setFlagged(true),
  },
]
```

Then render `<CommentActionBar actions={actions} />`, and show `flagged` however your card marks a flagged comment.

An entry with no `icon` always goes to the overflow menu, since an icon button with no icon is nothing to click.

## Remove or reorder an action

```typescript
// No copy link on this surface.
const actions = commentActions({
  comment,
  capabilities,
  onEdit,
  onDelete,
}).filter((action) => action.name !== 'Copy link')
```

Leaving `onCopyLink` out is the better version of that, because an action with no callback is never built in the first place. Filtering is for the case where the same callbacks are wired but one surface should not offer the action.

To reorder, build the list in parts and concatenate them. Each part keeps the default order within it, and the result goes to `CommentActionBar` as usual:

```typescript
// Edit first, so CommentActionBar promotes it to a button.
const actions = [
  ...commentActions({comment, capabilities, onEdit}),
  ...commentActions({comment, capabilities, onStatusChange, onDelete}),
]
```

> [!WARNING]
> Filtering on `action.name` breaks as soon as the names are translated. Build the list without the callback instead, or filter before the labels are applied. See [Translate the UI text](https://www.sanity.io/docs/collaboration/translate-the-ui).

## Own the flow an action starts

An action reports that the user asked for something. `CommentActionBar` does not own the flow an entry starts, which is what lets you replace the flow without replacing the action.

**Edit** flips your own state and renders `CommentInput`:

```tsx
const [editing, setEditing] = useState(false)

commentActions({comment, capabilities, onEdit: () => setEditing(true)})
```

**Delete** opens `DeleteCommentDialog`, and the dialog's confirm is what reaches the SDK:

```tsx
const [deletePending, setDeletePending] = useState(false)

commentActions({comment, capabilities, onDelete: () => setDeletePending(true)})

<DeleteCommentDialog
  open={deletePending}
  onClose={() => setDeletePending(false)}
  onConfirm={() => remove(comment.id)}
/>
```

The default `Comment` does both of these internally, which is why its `onEdit` takes a message rather than firing on the menu click.

## Let capabilities decide what is disabled

Per capability, the resolution order is the component's `capabilities` prop, then the provider's policy, then the built-in defaults. In a layout of your own, the first of those is the optional second argument to `resolveCapabilities(comment, overrides)`. `canResolve` covers reopening too, and `canCreate` covers retrying a failed create.

A policy goes on the provider. In an App SDK app that is `SDKCommentsProvider`. This one disables deletion on resolved comments:

```tsx
<SDKCommentsProvider
  capabilities={(comment) =>
    comment.status === 'resolved' ? {canDelete: false} : {}
  }
>
  {children}
</SDKCommentsProvider>
```

`CommentsProvider` takes the same `capabilities` prop. A role check goes there too:

```tsx
<CommentsProvider
  currentUser={me}
  getUser={getUser}
  // `isAdmin` is your app's own role check.
  capabilities={() => ({canDelete: isAdmin})}
>
  {children}
</CommentsProvider>
```

The defaults are author ownership for edit and delete, thread status for reply, and `true` for resolve, reactions, and `canCreate`, which covers retry. The resolution order in full is in [Providers](https://collaboration-platform.sanity.dev/?path=/docs/default-providers--docs#capabilities).

## Reactions are not actions

`AddReactionButton` opens its own popover rather than running a handler, so it composes beside `CommentActionBar`, or inside it as `children`, rather than as an entry in the array:

```tsx
<ActionRow align="center" gap={1}>
  <AddReactionButton
    reactions={comment.reactions}
    onReactionToggle={toggleReaction}
  />
  <CommentActionBar actions={actions} />
</ActionRow>
```

`ActionRow` is your card's own styled `Flex`, the one its hover reveal targets. See [Declare the hover reveal](https://www.sanity.io/docs/collaboration/build-a-custom-comment).

Give it `reactions` and it marks what the current user has already picked.

## Next steps

- The whole card assembled this way: [Build a custom comment](https://www.sanity.io/docs/collaboration/build-a-custom-comment).
- What `commentActions` builds, entry by entry: [Helpers](https://collaboration-platform.sanity.dev/?path=/docs/helpers--docs#commentactions).
- Why an action exists only when its callback does: [Comment components](https://www.sanity.io/docs/collaboration/components).



## Related changelog entries

Entries are listed newest first; each link points to the full entry as markdown. Follow one when you need to know what changed, when, or why — for example, to summarize recent updates, explain behavior that differs from older documentation, or check whether a fix has shipped.

- [@sanity/collaboration v0.3.0: New send shortcut, Cancel that clears the draft, and a discard action for failed comments](https://www.sanity.io/docs/changelog/e3fbc3a3-2d35-40e6-9309-786ec3e24b49.md) — October 29, 2026