On this page

This guide explains how thread header [`Communication.status`](/content/docs/api/fhir/resources/communication/index.html) models open versus closed threads, how to manage participants on the header with [`recipient`](/content/docs/api/fhir/resources/communication/index.html), and how FHIR [access policies](/content/docs/access/access-policies/index.html) align with who should see a thread.

For the underlying thread versus message shape, see [Messaging Data Model](/content/docs/communications/messaging-data-model/index.html).

## Thread Status [​](/content/docs/communications/thread-lifecycle-participants-access-control#thread-status "Direct link to Thread Status"/index.html)

The `status` field on a thread header controls whether the thread is active or closed. This is independent from `status` on individual messages, which reflects draft, sent, retracted, and similar states.

| Level | `status` meaning | Common values |
| --- | --- | --- |
| Thread header | Is the conversation open or closed? | `in-progress` (active), `completed` (closed), `entered-in-error` |
| Individual message | Message lifecycle | `preparation` (draft), `in-progress` (sent), `entered-in-error` (retracted or similar) |

For the full lifecycle including read tracking, see [Communication Lifecycle](/content/docs/communications/messaging-data-model#communication-lifecycle/index.html).

### Close a Thread [​](/content/docs/communications/thread-lifecycle-participants-access-control#close-a-thread "Direct link to Close a Thread"/index.html)

```ts
await medplum.patchResource('Communication', threadHeader.id, [{ op: 'replace', path: '/status', value: 'completed' }]);
```

To filter for only active threads when querying, see [Searching and Querying Threads](/content/docs/communications/searching-and-querying-threads/index.html).

### Reopen a Closed Thread [​](/content/docs/communications/thread-lifecycle-participants-access-control#reopen-a-closed-thread "Direct link to Reopen a Closed Thread"/index.html)

```ts
await medplum.patchResource('Communication', threadHeader.id, [
  { op: 'replace', path: '/status', value: 'in-progress' },
]);
```

caution
Closing a thread header does not automatically change the `status` of child messages; they are independent. A closed thread can still contain unread messages if you track read state separately (for example with Tasks). Decide in your UI whether to show those messages or filter by header status.

## Managing Participants [​](/content/docs/communications/thread-lifecycle-participants-access-control#managing-participants "Direct link to Managing Participants"/index.html)

Threads support multiple participants through the `recipient` array on the thread header. You can create group threads and add or remove participants over time.

### Create a Group Thread [​](/content/docs/communications/thread-lifecycle-participants-access-control#create-a-group-thread "Direct link to Create a Group Thread"/index.html)

Set `sender` to the thread creator and list all participants (including the creator) in `recipient`. For why this convention matters, see the [thread header `sender` / `recipient` recommendation](/content/docs/communications/messaging-data-model#building-and-structuring-threads/index.html).

```ts
const messagingGroupThread = await medplum.createResource({
  resourceType: 'Communication',
  status: 'in-progress',
  topic: { text: 'Care coordination - Homer Simpson' },
  subject: { reference: 'Patient/homer-simpson', display: 'Homer Simpson' },
  sender: { reference: 'Practitioner/doctor-alice-smith', display: 'Dr. Alice Smith' },
  recipient: [
    { reference: 'Practitioner/doctor-alice-smith', display: 'Dr. Alice Smith' },
    { reference: 'Practitioner/doctor-gregory-house', display: 'Dr. Gregory House' },
    { reference: 'Practitioner/nurse-jackie', display: 'Nurse Jackie' },
  ],
});

console.log(messagingGroupThread);
```

### Add a Participant [​](/content/docs/communications/thread-lifecycle-participants-access-control#add-a-participant "Direct link to Add a Participant"/index.html)

Use a JSON Patch to append to the `recipient` array:

```ts
await medplum.patchResource('Communication', messagingGroupThreadId, [
  {
    op: 'add',
    path: '/recipient/-',
    value: { reference: 'Practitioner/dr-wilson', display: 'Dr. Wilson' },
  },
]);
```

### Remove a Participant [​](/content/docs/communications/thread-lifecycle-participants-access-control#remove-a-participant "Direct link to Remove a Participant"/index.html)

Replace the `recipient` array without the removed person. Read the current header, filter, then patch:

```ts
const messagingThreadForParticipants = await medplum.readResource('Communication', messagingGroupThreadId);

const messagingUpdatedRecipients = messagingThreadForParticipants.recipient?.filter(
  (r) => r.reference !== 'Practitioner/nurse-jackie'
);

await medplum.patchResource('Communication', messagingGroupThreadId, [
  { op: 'replace', path: '/recipient', value: messagingUpdatedRecipients },
]);
```

caution
Adding or removing recipients on the thread header does not retroactively change recipients on existing child messages. When sending new messages in the thread, use the thread header's current recipient list so the conversation stays consistent.

If your access policies scope visibility to `Communication?recipient=%profile`, a removed participant may lose access to the thread header but can still read child messages where they were originally listed as a `recipient`. If full revocation is required, update child messages too or use a different access control approach (for example compartment-based policies).

## Access Control [​](/content/docs/communications/thread-lifecycle-participants-access-control#access-control "Direct link to Access Control"/index.html)

`recipient` and `sender` on [`Communication`](/content/docs/api/fhir/resources/communication/index.html) describe who should see a thread. [Access policies](/content/docs/access/access-policies/index.html) on the Medplum project enforce who can read and write those resources.

### Participant-Scoped Access [​](/content/docs/communications/thread-lifecycle-participants-access-control#participant-scoped-access "Direct link to Participant-Scoped Access"/index.html)

For typical messaging, restrict Communication reads so users only see threads where they appear as a `recipient` or `sender`. Configure that with an access policy on the project, not only in application code.

The example below uses two `resource` entries for Communication — one scoped to `recipient` and one to `sender`. Medplum ORs multiple entries of the same resource type, so the user sees Communications where they are a recipient or a sender:

```json
{
  "resourceType": "AccessPolicy",
  "name": "Messaging - Participant Access",
  "resource": [
    {
      "resourceType": "Communication",
      "criteria": "Communication?recipient=%profile"
    },
    {
      "resourceType": "Communication",
      "criteria": "Communication?sender=%profile"
    }
  ]
}
```

note
`AccessPolicy` `criteria` uses Medplum's search subset (only `:not` and `:missing` modifiers; no chained searches). Multiple `resource` entries of the same type are ORed. See [Access Policies](/content/docs/access/access-policies/index.html) for the full set of supported patterns before relying on criteria in production.

### Admin and Supervisor Access [​](/content/docs/communications/thread-lifecycle-participants-access-control#admin-and-supervisor-access "Direct link to Admin and Supervisor Access"/index.html)

Supervisors, compliance staff, or support roles often need to see all threads even when they are not participants. Use a separate access policy (or role) that grants broader Communication read access:

```json
{
  "resourceType": "AccessPolicy",
  "name": "Messaging - Supervisor Access",
  "resource": [
    {
      "resourceType": "Communication",
      "readonly": true
    },
    {
      "resourceType": "Task",
      "readonly": true
    }
  ]
}
```

Assign that policy to admin users as appropriate. The example above is read-only: supervisors can view threads but not send or change status unless you set `readonly` differently.

### Complex Access Patterns [​](/content/docs/communications/thread-lifecycle-participants-access-control#complex-access-patterns "Direct link to Complex Access Patterns"/index.html)

Multi-tenant messaging, role-based visibility, compartment rules, and cross-organization threads need careful policy design. See [Access Policies](/content/docs/access/access-policies/index.html) or contact [hello@medplum.com](mailto:hello@medplum.com) for guidance.

## In Your UI [​](/content/docs/communications/thread-lifecycle-participants-access-control#in-your-ui "Direct link to In Your UI"/index.html)

Show thread status visually in the thread list — for example labels or icons to distinguish active (`in-progress`) from closed (`completed`) threads. When a thread is closed, consider disabling the compose area so users don't accidentally send into a resolved conversation.

Render the participant list from the header's `recipient` array. Update it dynamically when participants are added or removed. Show the sender/creator so users know who started the conversation.

When you add a participant, verify they can actually query the thread: an updated `recipient` alone does not grant access if the new user's access policies don't cover the Communication. The thread will not appear in their searches until the policy matches. Testing this with two user accounts in different roles (see the tip above) is the most reliable way to catch policy gaps.

## See Also [​](/content/docs/communications/thread-lifecycle-participants-access-control#see-also "Direct link to See Also"/index.html)

- [Messaging Data Model](/content/docs/communications/messaging-data-model/index.html) — thread headers, messages, and key elements
- [Searching and Querying Threads](/content/docs/communications/searching-and-querying-threads/index.html) — queries and filters
- [Message Response Tracking and Routing](/content/docs/communications/message-response-tracking-and-routing/index.html) — Tasks as the source of truth for assignment when you use routing
- [Access Policies](/content/docs/access/access-policies/index.html) — criteria, compartments, and parameterized policies
- [Communication](/content/docs/api/fhir/resources/communication/index.html) FHIR resource API
- [Task](/content/docs/api/fhir/resources/task/index.html) FHIR resource API
- [Thread Status](/content/docs/communications/thread-lifecycle-participants-access-control#thread-status/index.html)  
  - [Close a Thread](/content/docs/communications/thread-lifecycle-participants-access-control#close-a-thread/index.html)  
  - [Reopen a Closed Thread](/content/docs/communications/thread-lifecycle-participants-access-control#reopen-a-closed-thread/index.html)  
- [Managing Participants](/content/docs/communications/thread-lifecycle-participants-access-control#managing-participants/index.html)  
  - [Create a Group Thread](/content/docs/communications/thread-lifecycle-participants-access-control#create-a-group-thread/index.html)  
  - [Add a Participant](/content/docs/communications/thread-lifecycle-participants-access-control#add-a-participant/index.html)  
  - [Remove a Participant](/content/docs/communications/thread-lifecycle-participants-access-control#remove-a-participant/index.html)  
- [Access Control](/content/docs/communications/thread-lifecycle-participants-access-control#access-control/index.html)  
  - [Participant-Scoped Access](/content/docs/communications/thread-lifecycle-participants-access-control#participant-scoped-access/index.html)  
  - [Admin and Supervisor Access](/content/docs/communications/thread-lifecycle-participants-access-control#admin-and-supervisor-access/index.html)  
  - [Complex Access Patterns](/content/docs/communications/thread-lifecycle-participants-access-control#complex-access-patterns/index.html)  
- [In Your UI](/content/docs/communications/thread-lifecycle-participants-access-control#in-your-ui/index.html)  
- [See Also](/content/docs/communications/thread-lifecycle-participants-access-control#see-also/index.html)
