Mastering FHIR Search: Parameters, Modifiers, Prefixes, Chaining and Paging
A practical reference to FHIR REST search with working example URLs — parameter types, modifiers, date and quantity prefixes, AND/OR, _include, chaining, sorting and paging.
By the FHIR Toolbox team, Omindra Labs · 5 min read ·
Checked against the official specifications listed under Standards and references at the end of this article.
FHIR search looks like an ordinary query string, but each parameter has a *type*, and the type decides how values are matched, which modifiers work and how dates and numbers are compared. Once you know the types, almost every search URL becomes predictable.
All examples below use [base] for your server's FHIR base URL (for example https://hapi.fhir.org/baseR4). Searches return a Bundle with type: "searchset".
The shape of a search
GET [base]/Observation?patient=Patient/123&code=http://loinc.org|8867-4&_sort=-date&_count=20
- The resource type in the path (
Observation) scopes the search.GET [base]?_type=Patient,Practitioner&name=smithsearches several types at once on servers that support system-level search. - Each
name=valuepair is a search parameter defined for that resource type. The list is in the resource's page of the specification and in the server's CapabilityStatement (GET [base]/metadata). POST [base]/Observation/_searchwith anapplication/x-www-form-urlencodedbody does the same search. Use it when parameters contain sensitive values you don't want in access logs, or when the URL gets too long.
Parameter types
| Type | Example | How it matches |
|---|---|---|
| string | name=shaw | Case- and accent-insensitive starts-with |
| token | gender=female, code=http://loinc.org|8867-4 | Exact match on a code, optionally with its system |
| date | birthdate=1990, date=ge2024-01-01 | Range comparison, honouring precision |
| reference | subject=Patient/123 | Points at a specific resource |
| quantity | value-quantity=gt100|http://unitsofmeasure.org|mg | Number with optional system and unit |
| number | probability=gt0.8 | Numeric comparison |
| uri | url=http://hl7.org/fhir/ValueSet/example | Exact URI |
| composite | component-code-value-quantity=http://loinc.org|8480-6$gt140 | Two values that must match on the same element |
| special | near=42.25|-83.69|10|km | Defined per parameter |
Strings
name=shaw matches "Shaw", "Shawcross" and "SHAW". Two modifiers change that:
name:exact=Shaw— exact, case-sensitive match on the whole string.name:contains=haw— substring match anywhere in the value.
name on Patient searches family, given, prefix, suffix and text together. Use family= or given= to target one part.
Tokens
Tokens match codes and identifiers. The system|code form avoids collisions between code systems:
GET [base]/Observation?code=http://loinc.org|8867-4 # code 8867-4 in LOINC
GET [base]/Observation?code=8867-4 # that code in any system
GET [base]/Observation?code=|8867-4 # code with no system
GET [base]/Patient?identifier=http://hospital.example.org/mrn|00123456
Useful token modifiers: :not (status:not=entered-in-error), :text (matches the display text, e.g. code:text=heart rate), and :in / :not-in for value set membership on servers with terminology support.
Tokens are case-sensitive for codes. gender=Female matches nothing, because the code is female.
Dates and prefixes
Dates and other ordered values accept a two-letter prefix:
| Prefix | Meaning |
|---|---|
eq | equal (the default) |
ne | not equal |
gt / lt | greater / less than |
ge / le | greater or equal / less or equal |
sa / eb | starts after / ends before |
ap | approximately |
Precision matters: birthdate=1990 matches anyone born at any point in 1990, because the value is treated as the range 1990-01-01 to 1990-12-31. date=2024-03 covers the whole of March.
For a date range, repeat the parameter (repeats are ANDed):
GET [base]/Encounter?patient=123&date=ge2024-01-01&date=lt2025-01-01
If you include a time, include a timezone: date=ge2024-01-01T08:00:00Z. URL-encode + offsets as %2B, or the server will read them as spaces.
Quantities
[prefix][number]|[system]|[code], where system and code identify the unit:
GET [base]/Observation?code=http://loinc.org|2339-0&value-quantity=gt180|http://unitsofmeasure.org|mg/dL
Leaving the system and unit off (value-quantity=gt180) compares numbers regardless of unit, which is rarely what you want for lab values.
References
GET [base]/Observation?subject=Patient/123
GET [base]/Observation?patient=123 # patient is a reference parameter restricted to Patient
GET [base]/Observation?subject:Patient=123 # type modifier
AND, OR and missing values
- OR: separate values with a comma —
status=final,amended. - AND: repeat the parameter —
date=ge2024-01-01&date=lt2024-07-01. - Missing:
:missing=truefinds resources where the element is absent;:missing=falsefinds those where it is present.Patient?birthdate:missing=trueis a quick data-quality check.
Following references: _include, _revinclude and chaining
# Observations plus the Patient each one points at
GET [base]/Observation?code=http://loinc.org|8867-4&_include=Observation:patient
# Patients plus every Encounter that references them
GET [base]/Patient?family=shaw&_revinclude=Encounter:subject
# Observations whose patient has the family name Shaw (chaining)
GET [base]/Observation?subject:Patient.family=shaw
# Patients who have at least one heart-rate observation (reverse chaining)
GET [base]/Patient?_has:Observation:patient:code=http://loinc.org|8867-4
Included resources appear in the same Bundle with entry.search.mode = "include"; matches have mode = "match". Count only the matches when you report results.
Controlling the result set
| Parameter | Effect |
|---|---|
_count=50 | Page size (servers may cap it) |
_sort=-date | Sort; - for descending, comma-separate multiple keys |
_summary=count | Only the total, no entries |
_summary=true | Only elements marked as summary |
_elements=id,name,birthDate | Only the listed top-level elements |
_total=accurate | Ask for an exact Bundle.total (can be slow) |
Paging
Never build page URLs yourself. Follow Bundle.link where relation = "next" until there isn't one:
"link": [
{ "relation": "self", "url": "https://fhir.example.org/r4/Patient?family=shaw&_count=20" },
{ "relation": "next", "url": "https://fhir.example.org/r4?_getpages=7f3a...&_getpagesoffset=20&_count=20" }
]
The next URL is often an opaque, server-specific token that expires, so page through results promptly rather than storing the links.
Unknown and ignored parameters
By default most servers silently ignore parameters they don't support, so a typo like birthDate=1990 (the parameter is birthdate) returns *every* patient. Two defences:
- Compare the
selflink in the response with what you sent. Servers usually echo only the parameters they applied. - Send
Prefer: handling=strictso the server rejects unknown parameters with an error instead.
A worked example
"Final blood pressure readings for patient 123 in 2024, newest first, with systolic over 140":
GET [base]/Observation
?patient=Patient/123
&code=http://loinc.org|85354-9
&status=final
&date=ge2024-01-01&date=lt2025-01-01
&component-code-value-quantity=http://loinc.org|8480-6$gt140
&_sort=-date
85354-9 is the LOINC blood pressure panel. Systolic (8480-6) and diastolic (8462-4) values live in Observation.component, which is why this needs the composite component-code-value-quantity parameter: it requires the code *and* the value to match on the same component.
Try it
The FHIR Query Builder builds these URLs from dropdowns, with the search parameters available for each resource type, modifiers and date prefixes. Running a query opens the URL against the server you choose (the public HAPI R4 test server by default). Only the query URL is sent, and only when you click run.
Standards and references
FHIR Toolbox is a free collection of HL7 FHIR tools by Omindra Labs. The tools process your data in your browser; it is not uploaded for normal tool operations.