> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ferrum.thalamiq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Search

> How to search resources with FHIR search parameters.

## 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:

```bash theme={null}
# 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"
```

<Note>
  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.
</Note>

## 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:

```bash theme={null}
# 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)

### Parameter name forms

Most resource parameters follow:

```text theme={null}
{code}[:{modifier}][.{chain}]=...
```

* `:{modifier}` is **case-insensitive** (except reference type modifiers like `:Patient`)
* `.{chain}` is single-level chaining (see below)

<Tip>
  Want to see which search parameters exist for a resource type? Check the
  server’s `CapabilityStatement`: `GET /fhir/metadata`.
</Tip>

### 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:

1. Be valid for the parameter type, and
2. 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:

```bash theme={null}
# 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"
```

<Note>
  Token modifiers `:in`, `:not-in`, `:above`, and `:below` are rejected
  (terminology-backed behavior is not implemented yet).
</Note>

### Chaining (single-level)

Ferrum supports single-level chaining on reference parameters:

* Basic chaining: `subject.name=peter`
* Type-restricted chaining: `subject:Patient.name=peter`

Examples:

```bash theme={null}
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:

```text theme={null}
_has:{referringResource}:{referringParam}:{filterParam}={value}
```

Example:

```bash theme={null}
# 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](https://hl7.org/fhir/searchparameter.html) that define:

* **What to index**: [FHIRPath expressions](https://hl7.org/fhir/fhirpath.html) extract values from resources
* **How to search**: [Parameter types](https://hl7.org/fhir/search.html#ptypes) 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                           |

<Note>
  Token and quantity parameters also support pipe-delimited forms: `system|code`
  (token), and `number||code` / `number|system|code` (quantity).
</Note>

```bash theme={null}
# 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`:

```bash theme={null}
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:

```bash theme={null}
# 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"):

```bash theme={null}
# 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):

```bash theme={null}
curl -G "http://localhost:8080/fhir/Patient" \
  --data-urlencode '_filter=name co "doe" and gender eq male'
```

<Note>
  `_filter` does not support modifiers (`_filter:...`) and should not be
  combined with multi-type system searches. Prefer a type-level search when
  using `_filter`.
</Note>

## Pagination (cursor-based)

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:

```bash theme={null}
# 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"
```

<Note>
  Ferrum parses `_offset` but does not use it for paging. Pagination links will
  not include `_offset`.
</Note>

## 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:

```bash theme={null}
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:

```bash theme={null}
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"
```

<Tip>
  `_count=0` is treated as `_summary=count` (per FHIR search result parameter
  rules).
</Tip>

## 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

## Related docs

* [Learn FHIR: Search](/learn-fhir/search)
* [Type-Level Search](/api-reference/search/type)
* [System-Level Search](/api-reference/search/system)
* [Compartment Search](/api-reference/search/compartment)
