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".

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=smith searches several types at once on servers that support system-level search.
  • Each name=value pair 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/_search with an application/x-www-form-urlencoded body 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

TypeExampleHow it matches
stringname=shawCase- and accent-insensitive starts-with
tokengender=female, code=http://loinc.org|8867-4Exact match on a code, optionally with its system
datebirthdate=1990, date=ge2024-01-01Range comparison, honouring precision
referencesubject=Patient/123Points at a specific resource
quantityvalue-quantity=gt100|http://unitsofmeasure.org|mgNumber with optional system and unit
numberprobability=gt0.8Numeric comparison
uriurl=http://hl7.org/fhir/ValueSet/exampleExact URI
compositecomponent-code-value-quantity=http://loinc.org|8480-6$gt140Two values that must match on the same element
specialnear=42.25|-83.69|10|kmDefined 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:

PrefixMeaning
eqequal (the default)
nenot equal
gt / ltgreater / less than
ge / legreater or equal / less or equal
sa / ebstarts after / ends before
apapproximately

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=true finds resources where the element is absent; :missing=false finds those where it is present. Patient?birthdate:missing=true is 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

ParameterEffect
_count=50Page size (servers may cap it)
_sort=-dateSort; - for descending, comma-separate multiple keys
_summary=countOnly the total, no entries
_summary=trueOnly elements marked as summary
_elements=id,name,birthDateOnly the listed top-level elements
_total=accurateAsk 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:

  1. Compare the self link in the response with what you sent. Servers usually echo only the parameters they applied.
  2. Send Prefer: handling=strict so 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.