Implementation Features
| Feature | Support | Notes |
|---|
| System-level Search | Full | Requires _type parameter |
| Type-level Search | Full | Primary search form |
| Compartment Search | Full | Supports specific type or all types (*) |
| GET Search | Full | Query string parameters |
| POST Search | Full | Form-urlencoded body |
| Parameter Types | Full | string, token, date, number, quantity, reference, uri, text, content |
| Value Prefixes | Full | eq ne gt lt ge le sa eb ap for date/number/quantity/_lastUpdated |
| Modifiers | Partial | Implemented subset; validated per type + SearchParameter metadata |
| Special Parameters | Full | _id, _lastUpdated, _in, _list, _text, _content |
| Chaining | Single-level | subject.name=peter, subject:Patient.name=peter |
Reverse chaining (_has) | Full | _has:Observation:subject:code=... |
| Membership Search | Full | _in and _list parameters |
_include / _revinclude | Full | Wildcards (*, Resource:*), :iterate (depth-limited) |
_filter (R5-style) | Full | Expression-based filtering with multiple operators |
| Pagination | Cursor-based | Keyset pagination with _cursor and _cursor_direction |
| Totals | Full | _total=accurate, _total=estimate, _total=none |
| Summary Modes | Full | _summary=count, _summary=text, _summary=data, _summary=true |
_elements | Full | Element filtering (when _summary not specified) |
| Sorting | Limited | Only _id and _lastUpdated supported |
| Unknown Parameters | Lenient | Ignored by default; strict mode via Prefer: handling=strict |
| Composite Parameters | Not Supported | Future work |
Endpoints Ferrum implements
| Scope | GET | POST | Notes |
|---|
| System-level | /fhir/?_type=A,B | /fhir/_search | _type is required |
| Type-level | /fhir/{resourceType}?… | /fhir/{resourceType}/_search | Primary search form |
| Compartment | /fhir/{Compartment}/{id}/{type} | /fhir/{Compartment}/{id}/{type}/_search | “All types” uses literal * |
Examples:
# Type-level search (GET)
curl "http://localhost:8080/fhir/Patient?name=Doe"
# Type-level search (POST)
curl -X POST "http://localhost:8080/fhir/Patient/_search" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "name=Doe"
# System-level search (GET)
curl "http://localhost:8080/fhir/?_type=Patient,Observation&_count=10"
# System-level search (POST)
curl -X POST "http://localhost:8080/fhir/_search" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "_type=Patient,Observation&_count=10"
# Compartment search (GET) - specific type
curl "http://localhost:8080/fhir/Patient/123/Observation?code=http://loinc.org|29463-7"
# Compartment search (GET) - all types
curl "http://localhost:8080/fhir/Patient/123/*?_type=Observation,Condition"
# Compartment search (POST)
curl -X POST "http://localhost:8080/fhir/Patient/123/Observation/_search" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "code=http://loinc.org|29463-7"
POST-based search must use Content-Type: application/x-www-form-urlencoded.
Ferrum merges query string parameters and body parameters into one ordered
parameter list.
Parameter occurrence semantics (AND/OR)
Ferrum preserves parameter occurrences in request order (no lossy map parsing):
- Repeating a parameter is AND:
name=John&name=Smith
- Comma-separated values are OR:
name=John,Smith
Examples:
# AND logic: name contains "John" AND name contains "Smith"
curl "http://localhost:8080/fhir/Patient?name=John&name=Smith"
# OR logic: name equals "John" OR "Smith"
curl "http://localhost:8080/fhir/Patient?name=John,Smith"
# Combined: (name=John OR name=Jane) AND gender=male
curl "http://localhost:8080/fhir/Patient?name=John,Jane&gender=male"
Search syntax (modifiers, prefixes, chaining)
Most resource parameters follow:
{code}[:{modifier}][.{chain}]=...
:{modifier} is case-insensitive (except reference type modifiers like :Patient)
.{chain} is single-level chaining (see below)
Want to see which search parameters exist for a resource type? Check the
server’s CapabilityStatement: GET /fhir/metadata.
Value prefixes
Ferrum supports the standard FHIR prefixes when they appear at the start of the value.
Applies to: date, number, quantity, and _lastUpdated.
| Prefix | Meaning (FHIR-style) | Examples |
|---|
eq | equal (default if omitted) | birthdate=eq1990-01-01 |
ne | not equal | value=ne5 |
gt | greater than | value=gt5 |
ge | greater or equal | date=ge2024-01-01 |
lt | less than | date=lt2024-02 |
le | less or equal | value=le10 |
sa | starts after | date=sa2024-01-01 |
eb | ends before | date=eb2024-01-01 |
ap | approximately (±10%, min 1 day for dates) | date=ap2024-01, value=ap100.0 |
Notes:
- Date precision is range-based (
2024 matches any date in 2024; 2024-01 matches any date in Jan 2024).
- For
number/quantity, eq/ne respect implied precision (FHIR-style decimal ranges).
Modifiers
Ferrum recognizes and validates a subset of FHIR modifiers. A modifier must:
- Be valid for the parameter type, and
- Be allowed by the underlying
SearchParameter metadata (when provided)
Common patterns:
| Modifier | Applies to | What it does in Ferrum |
|---|
:missing | most indexed parameter types | existence check (true = missing, false = present) |
:exact | string, _text, _content | exact match / phrase search |
:contains | string, uri, some reference | substring match (string/uri); hierarchy closure (reference) |
:text | string, token, reference | text match against human text fields (value/display) |
:code-text | token, reference | match code/id-like parts (case-insensitive starts-with) |
:text-advanced | token, reference | PostgreSQL full-text (websearch_to_tsquery) on display |
:not | token, _in, _list | negation (token set semantics; membership exclusion) |
:identifier | reference | token-style match against Reference.identifier |
:above / :below | reference, uri | hierarchy traversal (reference) / URL path ancestry |
:of-type | token | Identifier triple typeSystem typeCode value match |
:{ResourceType} | reference | reference type restriction (e.g. subject:Patient=123) |
Examples:
# Existence
curl "http://localhost:8080/fhir/Patient?birthdate:missing=true"
# String modifiers
curl "http://localhost:8080/fhir/Patient?name:exact=John%20Doe"
curl "http://localhost:8080/fhir/Patient?name:contains=doe"
# Token modifiers
curl "http://localhost:8080/fhir/Observation?status:not=final"
curl "http://localhost:8080/fhir/Patient?identifier:of-type=http://hl7.org/fhir/v2/0203|MR|12345"
# Reference modifiers (including type modifier)
curl "http://localhost:8080/fhir/DiagnosticReport?subject:Patient=123"
curl "http://localhost:8080/fhir/DiagnosticReport?subject:identifier=http://acme.example/mrn|123"
# URI hierarchy
curl "http://localhost:8080/fhir/NamingSystem?value:below=http://example.com/system"
Token modifiers :in, :not-in, :above, and :below are rejected
(terminology-backed behavior is not implemented yet).
Chaining (single-level)
Ferrum supports single-level chaining on reference parameters:
- Basic chaining:
subject.name=peter
- Type-restricted chaining:
subject:Patient.name=peter
Examples:
curl "http://localhost:8080/fhir/DiagnosticReport?subject.name=peter"
curl "http://localhost:8080/fhir/DiagnosticReport?subject:Patient.birthdate=ge1990"
Reverse chaining (_has)
Ferrum supports _has (reverse chaining) with the form:
_has:{referringResource}:{referringParam}:{filterParam}={value}
Example:
# Patients that have at least one final Observation with the given LOINC code
curl "http://localhost:8080/fhir/Patient?_has:Observation:subject:code=http://loinc.org|29463-7&_has:Observation:subject:status=final"
What is searchable?
Resources become searchable through SearchParameter resources that define:
- What to index: FHIRPath expressions extract values from resources
- How to search: Parameter types determine search semantics
- Where to store: Each type maps to a dedicated database table
Parameter types and tables
| Type | Database Table | Example Values |
|---|
string | search_string | name=John, name:contains=doe |
token | search_token | status=active, code=12345 |
date | search_date | birthdate=2020-01-01, date=ge2020 |
number | search_number | value=5.0, value=lt5.1 |
quantity | search_quantity | height=170, height=ge170 |
reference | search_reference | subject=Patient/123, subject.name=peter |
uri | search_uri | url=http://example.com, url:below=... |
text | search_text | _text=diabetes |
content | search_content | _content=diabetes |
composite | search_composite | Not yet supported |
Token and quantity parameters also support pipe-delimited forms: system|code
(token), and number||code / number|system|code (quantity).
# Token: system|code
curl "http://localhost:8080/fhir/Observation?code=http://loinc.org|29463-7"
# Quantity: number||code (no system)
curl "http://localhost:8080/fhir/Observation?value-quantity=ge5||mg"
# Quantity: number|system|code
curl "http://localhost:8080/fhir/Observation?value-quantity=ge5|http://unitsofmeasure.org|mg"
Unknown or unsupported parameters
By default, Ferrum follows the common “lenient” behavior and ignores unknown/unsupported search
parameters.
If you want strict validation, use Prefer: handling=strict:
curl -H "Prefer: handling=strict" \
"http://localhost:8080/fhir/Patient?definitely-not-a-param=1"
This returns an error listing the unknown/unsupported parameters for that resource context.
Advanced search features supported
_include / _revinclude
Ferrum supports _include and _revinclude including:
- Wildcards (
*, Resource:*)
:iterate (depth-limited to prevent infinite recursion)
- Deduplication of included resources
Examples:
# Include related resources
curl "http://localhost:8080/fhir/DiagnosticReport?subject=Patient/123&_include=DiagnosticReport:subject"
# Include with wildcard
curl "http://localhost:8080/fhir/Patient/123?_include=*"
# Reverse include (find resources that reference this one)
curl "http://localhost:8080/fhir/Patient/123?_revinclude=Observation:subject"
# Include with iteration (limited depth)
curl "http://localhost:8080/fhir/DiagnosticReport?subject=Patient/123&_include=DiagnosticReport:subject:iterate"
Membership search: _in and _list
Ferrum supports membership searches (e.g. “resources in Group/List”):
# Find patients in a Group
curl "http://localhost:8080/fhir/Patient?_in=Group/104"
# Exclude patients in a Group
curl "http://localhost:8080/fhir/Patient?_in:not=Group/104"
# Find observations for patients in a Group (chained)
curl "http://localhost:8080/fhir/Observation?subject._in=Group/104"
# Find patients in a List
curl "http://localhost:8080/fhir/Patient?_list=List/105"
_filter (FHIR R5-style expression filter)
Ferrum supports _filter expressions for type-level search (and for system search when a single
type is implied).
Operators supported include: eq, ne, co, sw, ew, re, gt, lt, ge, le, sa,
eb, ap, pr, in, ni, ss, sb.
Example (use --data-urlencode so spaces and quotes are encoded correctly):
curl -G "http://localhost:8080/fhir/Patient" \
--data-urlencode '_filter=name co "doe" and gender eq male'
_filter does not support modifiers (_filter:...) and should not be
combined with multi-type system searches. Prefer a type-level search when
using _filter.
Ferrum uses keyset/cursor pagination for stable paging over a changing dataset.
- Control page size with
_count
- Follow
Bundle.link URLs (next, prev, first, last) returned by the server
- Paging links use Ferrum’s internal parameters:
_cursor: base64url-encoded lastUpdated,id
_cursor_direction: next, prev, or last
Example:
# First page
curl "http://localhost:8080/fhir/Patient?_count=10"
# Next page (use the cursor from Bundle.link[].url in the response)
curl "http://localhost:8080/fhir/Patient?_count=10&_cursor=eyJsYXN0VXBkYXRlZCI6IjIwMjQtMDEtMDEiLCJpZCI6IjEyMyJ9&_cursor_direction=next"
Ferrum parses _offset but does not use it for paging. Pagination links will
not include _offset.
Totals and response shaping
_total
Ferrum calculates totals when requested via _total:
_total=accurate: compute an exact Bundle.total
_total=estimate: allow faster, estimated totals (where possible)
_total=none: omit totals entirely
Examples:
curl "http://localhost:8080/fhir/Patient?name=Doe&_total=accurate"
curl "http://localhost:8080/fhir/Patient?name=Doe&_total=estimate"
curl "http://localhost:8080/fhir/Patient?name=Doe&_total=none"
_summary and _elements
Ferrum applies _summary / _elements filtering to Bundle.entry[].resource:
_summary=count short-circuits fetching resources and returns only the count bundle
_summary takes precedence over _elements
- Includes are suppressed for
_summary=text (common server behavior)
Examples:
curl "http://localhost:8080/fhir/Patient?name=Doe&_summary=count"
curl "http://localhost:8080/fhir/Patient?name=Doe&_summary=text"
curl "http://localhost:8080/fhir/Patient?name=Doe&_elements=id,name,birthdate"
curl "http://localhost:8080/fhir/Patient?name=Doe&_summary=data"
_count=0 is treated as _summary=count (per FHIR search result parameter
rules).
Limits and configuration knobs
Search limits are configurable under fhir.search:
max_count (default 1000): maximum allowed _count
max_total_results (default 10000): maximum allowed _maxresults
max_includes (default 10): maximum number of _include + _revinclude
max_include_depth (default 3): maximum :iterate depth
enable_text / enable_content: enable _text / _content full-text search parameters
Known gaps
Current limitations in Ferrum’s search engine:
- Recursive chaining (e.g.
subject.organization.name) is not supported (single-level only)
- Composite search parameters are not supported