Skip to main content

Paginated Search

In this guide, we will explain how to perform paginated search queries using the FHIR specification. Specifically, we will discuss using the _count parameter to set the page size, the _offset parameter to set the page offset, and the Bundle.link field to retrieve subsequent pages.

Reminder

To get consistent output across pages, always remember to provide a _sort parameter to your paginated search. See "Sorting the Results" for more info.

Setting the page size with the _count parameter

To set the number of items returned per page, use the _count query parameter. In the Medplum API, the default page size is 20, and the maximum allowed page size is 1000.

Here's an example query that sets the page size to 50:

await medplum.searchResources('Patient', { _count: '50' });

In this example, the search will return up to 50 Patient resources per page.

Paginating with Included Resources

Pagination can be difficult when you are including linked resources, as you will not know how many of each resource will be returned. It may make sense to use chained searches instead so that only resources of one type are returned.

Setting the page offset with the _offset parameter

To set the page offset, or the starting point of the search results, use the _offset query parameter. This allows you to skip a certain number of items before starting to return results.

Here's an example query that sets the page offset to 30:

await medplum.searchResources('Patient', { _count: '50', _offset: '30' });

In this example, the search will return up to 50 Patient resources per page, starting from the 31st item in the result set.

Getting the total number of results with _total

To include the total count of matching resources in the search response, you need to use the _total parameter in your search query. This information is particularly useful for pagination and understanding the scope of the data you are dealing with.

The _total parameter can have three values: accurate, estimate, and none.

none (Default)No total is returned
estimateTells the Medplum server that you are willing to accept an approximate count. This is usually faster than the accurate option as it may use database statistics or other methods for estimating the total number without scanning the entire dataset. This option is particularly useful when you need a rough idea of the number of resources without requiring precision.
accurateThe Medplum server will perform additional processing to calculate the exact number of resources that match the search criteria. This can be more time-consuming, especially for large datasets, but you will receive a precise count. Use this option when an exact number is crucial for your use case.
warning

Because computing counts is an expensive operation, Medplum only produces estimated counts above a certain threshold.

  • Medplum first computes an estimated count.
  • If this count is above below the threshold, an accurate count is computed.
  • Otherwise, the estimated count is returned even if _total=accurate is specified.

For customers on the Medplum hosted service, this threshold is set to 1 million entries

For self-hosted customers, this threshold is server-level configuration called accurateCountThreshold (learn more).

By default, the search responses do not include totals. Choosing between accurate and estimate depends on your specific requirements. For large datasets, using estimate can significantly improve response times, but at the cost of precision.

Example Query Here is an example of how to use the _total parameter in a search query:

await medplum.search('Patient', { name: 'Smith', _total: 'accurate' });

This query will search for patients with the name "smith" and will return a Bundle with the accurate total number of matching resources included.

const response: Bundle = {
resourceType: 'Bundle',
id: 'bundle-id',
type: 'searchset',
total: 15,
entry: [
{
fullUrl: 'http://example.com/base/Patient/1',
resource: {
resourceType: 'Patient',
// ...
},
},
{
fullUrl: 'http://example.com/base/Patient/2',
resource: {
resourceType: 'Patient',
// ...
},
},
// ...
],
// ...
};
Note

The Medplum SDK provides the searchResources helper function. This function unwraps the response bundle of your search results and returns an array of the resources that match your parameters. If you want to get the count when using this function, the .bundle property is added to the array. You can access the total using response.bundle.total.

When you perform a paginated search, the response will be a Bundle resource containing a list of resources matching the query. The Bundle resource will also have a link field containing navigation links to help you traverse through the search results.

The Bundle.link field will include the following relations:

  • self: The URL of the current search results page.
  • first: The URL of the first page of search results.
  • previous: The URL of the previous page of search results (if applicable).
  • next: The URL of the next page of search results (if applicable).

Here's an example of how the Bundle.link field may look :

'link': [
{
relation: 'self',
url: 'https://example.com/Patient?_count=50&_offset=60',
},
{
relation: 'first',
url: 'https://example.com/Patient?_count=50&_offset=0',
},
{
relation: 'previous',
url: 'https://example.com/Patient?_count=50&_offset=10',
},
{
relation: 'next',
url: 'https://example.com/Patient?_count=50&_offset=110',
}
];

To navigate between pages, simply follow the URLs provided in the Bundle.link field.

The searchResourcePages() method of the MedplumClient provides an async generator to simplify the iteration over resource pages.

for await (const patientPage of medplum.searchResourcePages('Patient', { _count: 10 })) {
for (const patient of patientPage) {
console.log(`Processing Patient resource with ID: ${patient.id}`);
}
}

The array returned by searchResourcePages also includes a bundle property that contains the original Bundle resource. You can use this to access bundle metadata such as Bundle.total and Bundle.link.

Conclusion

In this guide, we've discussed how to perform paginated search queries using the _count and _offset search parameters, as well as the the Bundle.link element.