Searching and Querying Threads | Medplum

Search Query Cheat Sheet

Goal Query Notes
All threads Communication?part-of:missing=true Returns only thread headers
Messages in a thread Communication?part-of=Communication/{id}&_sort=sent Sorted chronologically
Threads for a patient Add &subject=Patient/{id} to any query above Filter by patient context
Threads I participate in Add &recipient=Practitioner/{id} Header must list the creator in recipient; see note below and modeling tip
Active threads only Add &status:not=completed,entered-in-error,stopped,unknown Exclude closed threads

Query for All Threads

// Search reference: find all thread headers, sorted by most recently active

await medplum.searchResources('Communication', {

'part-of:missing': true,

'status:not': 'completed,entered-in-error,stopped,unknown',

_sort: '-_lastUpdated',

});
medplum get 'Communication?part-of:missing=true&status:not=completed,entered-in-error,stopped,unknown&_sort=-_lastUpdated'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true&status:not=completed,entered-in-error,stopped,unknown&_sort=-_lastUpdated' \
  -H 'authorization: Bearer $ACCESS_TOKEN' \
  -H 'content-type: application/fhir+json'

The :missing modifier finds Communication resources that have no partOf reference — these are thread headers. Adding status:not=completed filters to only active threads. Sorting by -_lastUpdated (descending) puts the most recently active threads first.

Query for Messages in a Thread

// Retrieve all messages in a thread, sorted chronologically

await medplum.searchResources('Communication', {

'part-of': `Communication/${threadHeader.id}`,

_sort: 'sent',

});
medplum get 'Communication?part-of=Communication/{threadHeaderId}&_sort=sent'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of=Communication/{threadHeaderId}&_sort=sent' \
  -H 'authorization: Bearer $ACCESS_TOKEN' \
  -H 'content-type: application/fhir+json'

This retrieves all child messages for a given thread header, sorted chronologically by the sent timestamp.

Add Filters

Filter threads by patient:

// Filter threads to a specific patient

await medplum.searchResources('Communication', {

'part-of:missing': true,

subject: 'Patient/homer-simpson',

});
medplum get 'Communication?part-of:missing=true&subject=Patient/homer-simpson'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true&subject=Patient/homer-simpson' \
  -H 'authorization: Bearer $ACCESS_TOKEN' \
  -H 'content-type: application/fhir+json'

Filter by Current User

To show threads the current user participates in, filter by recipient:

// Filter to only the current user's active threads

await medplum.searchResources('Communication', {

'part-of:missing': true,

recipient: getReferenceString(profile),

'status:not': 'completed,entered-in-error,stopped,unknown',

});
medplum get 'Communication?part-of:missing=true&recipient=Practitioner/{id}&status:not=completed,entered-in-error,stopped,unknown'
curl 'https://api.medplum.com/fhir/R4/Communication?part-of:missing=true&recipient=Practitioner/{id}&status:not=completed,entered-in-error,stopped,unknown' \
  -H 'authorization: Bearer $ACCESS_TOKEN' \
  -H 'content-type: application/fhir+json'

This works when thread headers list all participants — including the thread creator — in recipient. The Medplum ThreadInbox component follows this convention: when a new thread is created, the sender is added to recipient alongside other participants, so a recipient-based query returns threads the user started and threads they were added to.

For other items to filter on, see the Communication Search Parameters.

Live Updates

To receive new messages in real time, subscribe to Communications in the current thread. WebSocket subscriptions may need to be enabled for your project (for example, the websocket-subscriptions feature). The subscription criteria for a specific thread:

Communication?part-of=Communication/{threadId}

When a notification arrives, append the new message to your local state. The notification Bundle contains a SubscriptionStatus entry and the new Communication — find it by resourceType rather than by position:

const threadId = 'example-thread-id';

const emitter = medplum.subscribeToCriteria(`Communication?part-of=Communication/${threadId}`);

emitter.addEventListener('message', (event) => {

const newMessage = event.payload.entry?.find((e) => e.resource?.resourceType === 'Communication')?.resource;

if (newMessage) {

console.log(newMessage);

}

});

If using @medplum/react, the useSubscription hook handles connection management:

useSubscription(

`Communication?part-of=Communication/${id}`,

(bundle: Bundle) => {

const newMessage = bundle.entry?.find(

(e) => e.resource?.resourceType === 'Communication'

)?.resource as Communication | undefined;

if (newMessage) {

// Show a toast or in-app notification for the new message

setMessages((prev) => [...prev, newMessage]);

}

}

);

See the useSubscription documentation for WebSocket connection setup and reconnection handling.