Search
How-to's
14 Min Read

Understanding FHIR Search Parameters

Christiaan Knaap
Christiaan Knaap

Subscribe to our newsletter

Subscribe

This post was originally published on May 13, 2020.

A shed in your backyard, a closet in your spare room, the balcony on the north that you never use anyway. Everyone has it. That place where ‘stuff’ is stored. And exactly how valuable is that stuff if you can’t find it anymore? That is what search in FHIR is for. And since not everyone gets the concept right from the specification, we’ll go over some examples using Firely Server. We will even go as far as defining your own search parameter!

The other day l was asked: “I’m trying to do a FHIRPath query on the locally deployed Firely Server. When I try almost any FHIRPath query on Patient it returns the full list of patients.  Is there something wrong with my syntax?”

GET localhost:8080/Patient?_format=json&Patient.where(id = ‘4342418’)

It was then that I decided to write this blogpost. Because the FHIRPath syntax is actually correct. But the understanding of the search part of the FHIR RESTful API is wrong. In short:

  • FHIR search is based on search parameters that are defined upfront.
  • A search parameter is defined in terms of FHIRPath
  • Many search parameters are defined as part of the FHIR specification
  • If your query is not covered by those, you can define your own search parameter, add it to the server and then use it in the search API.

In the subsequent examples, I will use the placeholder <fs> as the base address for Firely Server. You can substitute it for https://server.fire.ly to use the public test server or localhost:<port> if you run it locally (download your evaluation copy). All examples work in both STU3 and R4. Firely Server supports both, just use the correct fhirVersion mediatype parameter to select your preferred version. E.g.:

PUT <fs>/Patient/123
Accept=application/fhir+json;fhirVersion=4.0
Content-Type=application/fhir+json;fhirVersion=4.0
Body
{
   ...
}

Search with search parameters

We will analyze what was wrong with the original query and look at the correct way to do the search. But first, we need to create a Patient resource that matches the query so we get some results. We’ll also include a Patient resource that does not match the query.

PUT <fs>/Patient/4342418
{
"resourceType": "Patient",
"id": "4342418"
}

PUT <fs>/Patient/abc
{
"resourceType": "Patient",
"id": "abc"
}

Analyzing errors

Now let’s issue the original query and analyze the response.

GET <fs>/Patient?Patient.where(id = ‘4342418’)

The response will be a Bundle containing:

  • both Patients
  • an OperationOutcome stating
    "text": "Reference parameter 'Patient' is not supported."
  • a self link with
    "url": "<fs>/Patient?_sort=-_lastUpdated&_count=10&_skip=0"

Let’s start with the latter. The specification tells us that “In order to allow the client to be confident about what search parameters were used as criteria by the server, the server SHALL return the parameters that were actually used to process the search.” As you can see the only parameter that we tried to use is not in the self-link. So apparently, this was not processed by the server.

The OperationOutcome tells us why the parameter was not processed. It is not supported. Why it complains about a “Reference parameter” is for the next article.

The specification also tells us here that “In general, servers SHOULD ignore unknown or unsupported parameters for the following reasons: […]” So that explains why both Patient resources are in the result. With the single query parameter ignored, no parameters are left and all Patient resources match the query.

The correct way

The correct way to do the search from the original question is:

GET <fs>/Patient?_id=4342418

The response will again be a Bundle. This time containing:

  • Only the Patient resource with the matching id
  • a self link with
    "url": "<fs>/Patient?_id=4342418&_sort=-_lastUpdated&_count=10&_skip=0"

As you can see from the self-link, Firely Server processed the provided query parameter.

This query uses the predefined search parameter _id, available for every resourcetype. It is defined in the specification under the ‘Standard Parameters‘ on the search page. This definition does not show you the related FHIRPath and other details of the searchparameter though. To get more details, you can simply ask Firely Server:

GET <fs>/administration/SearchParameter?code=_id

Here we are using the search API to find our searchparameter in the administration part of Firely Server. It returns a Bundle containing the SearchParameter resource that defines ‘_id’.

Now inspect the contents of the searchparameter. The key parts are:

  • url: http://hl7.org/fhir/SearchParameter/Resource-id
    this is the so-called canonical url of the parameter, used to identify it across different FHIR servers and the specification itself.
  • code: _id
    this is what you use as the parametername in your search query
  • base: Resource
    this parameter is valid for the resourcetype ‘Resource’. For most parameters this contains a more specific type of resource like ‘Organization’ or ‘Patient’. Parameter can be valid for multiple types of resources, hence the array.
  • type: token
    token parameters are used for exact matches on coded values. Other types of parameters are string, number, quantity etc.
  • expression: id
    this is the actual FHIRPath expression. It selects an element in the resource that will be matched against the provided argument. It is always evaluated in the context of the base, so here ‘Resource.id’.

As an exercise, update each of the Patient resources by adding a name (Fred and Bob), and then search by the Patient.name parameter:

GET <fs>/Patient?name=Bob

Also, try to find the definition of the search parameter at the administration endpoint.

Search parameters in the specification

If you want to know whether there is already a search parameter that expresses what you need in your query, you can check the specification. On every resourcetype page, at the bottom, there is a list of defined search parameters for that resourcetype. Eg. http://hl7.org/fhir/R4/patient.html#search.

The common parameters, like ‘_id’ are listed at http://hl7.org/fhir/R4/search.html#all.

To find all the search parameters on a single page, visit http://hl7.org/fhir/R4/searchparameter-registry.html.

Beyond the standard search parameters

Sometimes the search parameters defined in the specification are not sufficient. A common case is when you have defined an extension to add information to a resource that is specific to your region or use case. As an example, we will use an extension defined by the FHIR specification, but for which there is no search parameter:

https://simplifier.net/Simplifier.Core.R4.Extensions/assembly-order/~overview

This extension defines how the different parts of a HumanName must be assembled to form the full name. It is bound to a ValueSet having three values:

  • F: Prefix Family Given Suffix
  • G: Prefix Given Family Suffix
  • UNK: Unknown

Let’s update our friends Fred and Bob on this extension. And give them a Family name, otherwise, the order of the names is of no meaning.

PUT <fs>/Patient/4342418
{
	"resourceType": "Patient",
	"id": "4342418",
	"name": [
		{
			"given": ["Fred"],
			"family": "Flintstone",
			"extension": [
				{
					"url":"http://hl7.org/fhir/StructureDefinition/humanname-assembly-order",
					"valueCode": "F"
				}
			]
		}
	]
}
PUT <fs>/Patient/abc
{
	"resourceType": "Patient",
	"id": "abc",
	"name": [
		{
			"given": ["Bob"],
			"family": "Hope",
			"extension":[
				{
					"url": "http://hl7.org/fhir/StructureDefinition/humanname-assembly-order",
					"valueCode": "G"
				}
			]
		}
	]
}

Now we would like to search for Patients that prefer their names spelled with their family names first. In this case, Fred. Looking at the search parameters for Patient, there is no parameter defined that selects this extension. So, we have to define one ourselves!

Defining a SearchParameter

A SearchParameter is a resource like any other in FHIR. We covered the key elements of it before. The hardest part to find out is the expression. You can use the excellent FhirPathTester tool by Brian Postlewaith (on Github and in the Windows Store) to find the right FHIRPath expression. In this case, we need:

Patient.name.extension.where(url='http://hl7.org/fhir/StructureDefinition/humanname-assembly-order').value

We’ll put the ‘Patient’ part in the base and the rest in the expression. Since the value in the extension is of type ‘code’, we need a search parameter of type ‘token’ to search on it. And we need to choose a canonical URL and a code by which to identify this parameter.

Note that we need to send the search parameter to the administration endpoint of Firely Server. The resources at the administration endpoint define how Firely Server behaves. If you would send it to the regular data endpoint, it would succeed but you could not use the search parameter for searching in Firely Server.

PUT <fs>/administration/SearchParameter/Patient-assembly-order
{
	"resourceType": "SearchParameter",
	"id": "Patient-assembly-order",
	"url": "http://example.org/fhir/SearchParameter/Patient-assembly-order",
	"name": "assembly-order",
	"status": "draft",
	"experimental": false,
	"publisher": "Firely Training Services",
	"code": "assembly-order",
	"base": [
		"Patient"
	],
	"type": "token",
	"description": "Value of the humanname-assembly-order extension",
	"expression": "name.extension.where(url='http://hl7.org/fhir/StructureDefinition/humanname-assembly-order').value",
}

Can you immediately search using this parameter? You can. But remember that the data added to the server earlier (like Fred and Bob above) will not be indexed for this parameter. So if you now try:

GET <fs>/Patient?assembly-order=F

You will get an empty resultset. The parameter is recognized and processed, but none of the resources will match because the search index in Firely Server has not been updated yet. Let’s solve that:

POST <fs>/administration/reindex/searchparameters?include=Patient.assembly-order
Accept=application/fhir+json;fhirVersion=4.0

Note that you also need to include the Accept header with a fhirVersion in this command. Otherwise, the STU3 resources will be reindexed.

Firely Server will respond immediately stating that it is reindexing. As long as that process is running, the search results can not be reliable so Firely Server will return HTTP code 423 ‘Locked’. Here there are only 2 resources and 1 search parameter. This means it will be done before you can send your query again. But in production systems with millions of resources, this is something you need to plan for (or contact us). More background on reindexing is in the Firely Server documentation.

Now try our query again:

GET <fs>/Patient?assembly-order=F

And yes, we get a bundle with only Fred in it!

Recap

FHIR Search is defined in terms of predefined search parameters, not in arbitrary FHIRPath statements.

You can add custom search parameters to Firely Server to cover cases that are not covered by the parameters from the specification. In a custom search parameter, you can use your dedicated FHIRPath expression.

Further steps

Firely Server does not only allow you to add your own search parameters. You can also add extensions and profiles (in the form of StructureDefinition resources) and even custom resourcetypes! And of course, you don’t need to add everything by hand, just let Firely Server load it from your Simplifier project, or from a public project, e.g. for your region. In the documentation, this is all described as ‘Controlling the conformance resources‘.

Want to stay on top of Interoperability and Health IT? 

Subscribe to our newsletter for the latest news on FHIR. 

Post a comment

Your email address will not be published. Required fields are marked *