How do you make your FHIR queries better?
You start by understanding what’s possible and what’s not possible on your own FHIR server. Not all servers are equal. Not all servers implement all features. And not all features that should work actually work — as I discovered when writing this post.
Querying FHIR is a big topic. I’ll be looking at three specific areas.
1. Search modifiers
Changing how a search parameter works.
2. Composite search parameters
Searching on a combination of two elements.
3. POSTing FHIR queries
Keeping sensitive data out of the query URL
Let’s get started.
Four key search modifiers
Modifiers are additions to query parameters that change the behavior of the parameter. They allow us to focus the search in ways that better refine the results.
Here are four of the most useful search modifiers:
1. :not
We’re used to searching for something that IS a specific value. The patient’s name is “Smith” for example. But did you know that you could also search for something that is NOT a specific value?
Status is a good example.
Most of us have written queries that filter resources based on the status. Maybe we want to see Procedures that are “in-progress” or DiagnosticReports that are “preliminary.”
Or maybe we want to see all Procedures apart from those with a status of “entered-in-error.”
Here’s how that looks using the :not modifier:
http://hapi.fhir.org/baseR4/Procedure?status:not=entered-in-error
Much simpler than listing all the statuses we DO want in the query.
2. :missing
This turns the search parameter into a boolean and allows us to filter results on whether or not a value exists. This is one of the most useful search modifiers. Here are some examples:
Give me all DiagnosticReports that do not yet have an issued date:
http://hapi.fhir.org/baseR4/DiagnosticReport?issued:missing=true
Give me all Patients that have a family name but do not have a given name. Here, we get to use the :missing modifier twice:
http://hapi.fhir.org/baseR4/Patient?given:missing=true&family:missing=false
3. :exact
Name searches are usually wildcard searches and case insensitive. The :exact modifier lets us force a precise match on a particular string, right down to the character case. Let’s say you want all patients with the given name JOE, entered in uppercase.
http://hapi.fhir.org/baseR4/Patient?given:exact=JOE
A good real world example might be filtering names for middle initials, which often appear as given names.
http://hapi.fhir.org/baseR4/Patient?given=Alex&given:exact=P
This returns a few results for ALEX P CORDOVA.
4. :contains
A sub-string search. Very useful when you know part of the string value but not the whole. An example might be a search for Devices with different model numbers, all of which begin with the same few characters.
First, using what we learned a minute ago, let’s find a Device resource where the Device.model is populated:
http://hapi.fhir.org/baseR4/Device?model:missing=false
The third result has a model of “HGT56789114test.”
Let’s assume that “HGT56” is the prefix for a series of models we now want to find, but that the remainder of the string value might differ. The following query using the :contains modifier returns 9 matching resources.
http://hapi.fhir.org/baseR4/Device?model:contains=HGT56
I can see a number of very specific use cases for :contains, particularly around less common and difficult to spell human names.
Composite Search Parameters
There are some queries you just can’t write in FHIR — not using collections of solitary search parameters. To illustrate this, let’s look at Observation.
Its component element allows us to store a list of values of different types, and to search on the contents of that list.
Let’s say we want to run a search query to identify cases of very significant high blood pressure based on a Diastolic value of over 100. The blood pressure readings are stored in two components, one for Systolic and one for Diastolic.

You might think that this query will give you what you want:
http://server.fire.ly/Observation?code=55284-4&component-code=8462-4&component-value-quantity=gt100
You’d be wrong. Let’s break it down to see why.
We’re asking for Observations with a Loinc Code of 55284-4 — “Blood Pressure.” For the sake of conciseness I’ve left out the system value.
Next we’re insisting that the component list contains both a “Diastolic Blood Pressure” value AND a quantity value over 100.
What we’re not doing is insisting that the “greater than 100” reading BE a Diastolic value. It could be a Systolic value — which is almost always over 100 for a healthy adult!
This is a very real problem, one that can’t be solved using simple search parameters.
For this we need a Composite.
A Composite is a search parameter that searches across two elements.
Only a small number of composite search parameters exist and many are part of the Observation resource. Here’s what our query would look like using the component-code-value-quantity search parameter, which joins the component-code and component-value-quantity parameters that we used earlier.
https://server.fire.ly/Observation?code=55284-4&component-code-value-quantity=8462-4$gt100
The $ sign separates the two values we’re searching for in the query string and joins them together into a single search parameter.
I mentioned earlier that not all FHIR servers are equal. This is noticeably true in their ability to handle composite searches.
The above queries all ran successfully and as expected on Firely and Azure servers, but were unsuccessful on the HAPI server.
POST Queries in FHIR
I worked with FHIR for a year before I knew it was possible to run search queries as POST requests. At first glance I couldn’t see a use case for this, but when you consider the sensitivity of the data that can appear in queries that use case quickly emerges.
Developers have a habit of logging everything. An API call comes in, we log where it came from. A long and detailed query string? We log it, just for reference.
The problem with this in a healthcare setting is that some of the data coming in as part of FHIR queries could and often is PI or PII data, which should never be written to causal log files.
– Patient names and dates of birth
– Identifiers, social security or medical reference numbers
– Next of kin, etc.
Sending the query as a POST request reduces the likelihood of values like these ending up somewhere they shouldn’t.
Here’s what it looks like in Postman:

A _search parameter identifies the POST request as a search query. The parameters are listed as form encoded data and the results are returned in a recognizable bundle.
There is no difference in the behavior of the query once it is received by the FHIR server. The returned bundle is the same as if it were a GET request.
---