---
{
  "title": "Introduction to FHIR Terminology: The Basics",
  "description": "The second post in a series on FHIR terminology: coded values, code systems, value sets, and concept maps",
  "date": "2026-08-17",
  "author": "Orlando Osorio",
  "tags": ["Terminology", "FHIR", "Tutorial"],
  "extra-scripts": [
    "/assets/js/demo/termbox/base.js",
    "/assets/js/demo/termbox/basics.js"
  ]
}
---

> For the complete documentation index, see [llms.txt](https://www.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

This is the second post in the [Introduction to FHIR Terminology Series](/blog/introduction-to-fhir-terminology). We'll go over the core concepts that make up the FHIR Terminology Module. We'll see concrete examples of what problems it can solve. And give an overview of how the different pieces fit together.

## Use Cases

As we [mentioned](/blog/introduction-to-fhir-terminology#why) before, we've seen newcomers to FHIR underutilize the terminology solutions. We believe part of the reason is a lack of awareness of what problems it can solve, some people might associate _Terminology_ with topics like standards, conformance, compliance, regulations. So we think a good place to start is to show a few concrete use cases that are easily implemented by using a terminology server. In fact, all these examples are running live against our sandbox instance.

Each tab shows one live example, from search boxes and dropdowns to mapping and translation; open the drawer at the bottom of the box to see the actual requests and responses[^1].


<div id="examples"></div>

## Background

When a physician writes "heart attack" and another writes "myocardial infarction", they mean the same thing. But for a software system, or any workflow at scale, these are just two different strings of text. Being able to share meaning between parties is paramount for interoperability. This is what healthcare terminologies are for. One of the earliest examples of a healthcare terminology is the International Classification of Diseases (ICD), adopted in 1893 to codify causes of death. It predates software and it's still in use today.

We'll be working with a set of constructs that fall under the definition of _a terminology_: taxonomies, ontologies, nomenclatures, code sets, vocabularies, classifications. For practical purposes, we'll call them _terminologies_ and will define them as a set of terms with a stable machine-readable identifier. They might be infinite, have synonyms, properties, relationships, structure, etc. But the main characteristic we'll need is for each element to have a stable identifier (_code_) and some textual label (_term_/_display_).

In "normal" web development, implementers use terminologies all the time (even if they're not called that). They're frequently implemented as enums or a table of items with a CRUD admin view (it's very common for the enums to be their own codes and for the items tables to use the database id as their identifier). The examples above could very well have been implemented using these patterns.

FHIR provides a [Terminology Module](https://build.fhir.org/terminology-module.html) designed to solve these and other problems. One interesting aspect is that the same abstractions are able to express standard, compliant, authoritative, large ontologies as well as enum-like, short, local lists of codes.

## Coded Values

Many FHIR resources will have fields whose domain of possible values is taken from a list (a terminology). Their values are _codes_ assigned elsewhere that identify a defined _concept_. For example:

```yaml
resourceType: Patient
name:
  - given: [Jane]
    family: Doe
gender: female
```

Notice that, in this case, `name` is an unbound property: it can take any human name, while `gender` can only take one of four values: `male`, `female`, `other`, `unknown`. In this case, `female` is a _coded value_.

A coded value is mainly a pair composed of "system" and "code", where `system` is a URL[^2] that identifies the code system (terminology) that defines the codes. This decision has significant implications on how discoverable coded values are: instead of finding a string in the wild that happens to look like a LOINC code, coded values are accompanied by their code system identifier. In the gender case above, the system is implicit (Administrative Gender, `http://hl7.org/fhir/administrative-gender`), we'll look at why shortly.

FHIR [defines](https://build.fhir.org/terminologies.html) 3 main data types to represent these codes[^3]: `code`, `Coding`, and `CodeableConcept`.

- [`code`](https://build.fhir.org/datatypes.html#code): The instance represents the code only. The system is implicit.
- [`Coding`](https://build.fhir.org/datatypes.html#Coding): The datatype contains a code and a system that identifies where the definition of the code comes from.
- [`CodeableConcept`](https://build.fhir.org/datatypes.html#CodeableConcept): A type that represents a concept by plain text and/or one or more `coding` elements.

Most coded elements in FHIR use `CodeableConcept` since it provides the greatest flexibility. It allows multiple codings and allows for free text to convey, for example, what a data enterer actually typed or cases where no code is available. It also helps with transition, since you can include old and new codes in the same structure (see Observation example below).

The `Coding` datatype is used when the intention is to reference a specific code, not to express a concept generally. The use of `Coding` is rare as it's less flexible than `CodeableConcept` and doesn't allow free text or translations.

The `code` datatype usually appears in enum-like scenarios. Typically these elements have a binding strength of `required`, i.e.: this code is required to come from the specified value set and there's no need to convey an alternative code. Usually these elements might be involved in business logic behavior, like dispatching on a value, exact comparison, etc. For example: a resource status, client code might ignore `retired` resources or act only on `active` ones.

### Binding

In the previous datatypes, the codes are of type `string`, the way to restrict the set of values they can take is via [bindings](https://build.fhir.org/terminologies.html#binding). The spec _binds_ the element to a _value set_, meaning it can only take values from that list.

Bindings have two main properties: `valueSet` and `strength`. `valueSet` defines which codes are valid for this element. `strength` refers to how the binding should be understood: `required`, `extensible`, `preferred`, `example`.

<div class="narrow">

![Binding example](binding.png "Patient spec fragment. Notice the highlighted elements, their datatypes, binding value sets and strengths")

</div>

In the example above, `Patient.gender` is bound to the `Administrative Gender` value set, with a `required` strength; therefore, `gender` can only be: `male`, `female`, `other`, `unknown`. And `maritalStatus` is bound to `Marital Status Codes`, with an `extensible` strength, meaning: the codes should come from that value set unless the expected code is not covered.

### Example coded values

{% tabs %}
{% tab title="Patient" %}
```yaml
resourceType: Patient
name:
  - given: [Jane]
    family: Doe
gender: female # code / AdministrativeGender (implicit)
maritalStatus: # CodeableConcept
  text: Married
  coding:
    - system: http://terminology.hl7.org/CodeSystem/v3-MaritalStatus
      code: M
      display: Married
```
{% endtab %}
{% tab title="Location" %}
```yaml
resourceType: Location
name : South Wing Neuro OR 1
status: suspended # code / LocationStatus (implicit)
operationalStatus: # Coding
  system: http://terminology.hl7.org/CodeSystem/v2-0116
  code: H
  display: Housekeeping
type: # CodeableConcept
  coding:
    - system: http://terminology.hl7.org/CodeSystem/v3-RoleCode
      code: RNEU
      display: Neuroradiology unit
form: # CodeableConcept without codings
  text: Room
```
{% endtab %}
{% tab title="Observation" %}
```yaml
resourceType: Observation
status: final # code / ObservationStatus (implicit)
code: # CodeableConcept with local and standard codings
 coding:
   - system: http://acmelabs.org
     code: 104177
     display: "Blood culture"
   - system: http://loinc.org
     code: 600-7
     display: Bacteria identified in Blood by Culture
valueCodeableConcept: # CodeableConcept
  coding:
    - system: http://snomed.info/sct
      code: 3092008
      display: Staphylococcus aureus
interpretation:
  text: Positive
  coding:
    - system: http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation
      code: POS
```
{% endtab %}
{% endtabs %}

## Resources

We'll now briefly look at the FHIR resources involved in the Terminology Module. In practice, you rarely create these directly. They're usually maintained elsewhere and loaded[^4] into (or from) a terminology server.

The Terminology Module defines four main terminology resources: [CodeSystem](https://build.fhir.org/codesystem.html), [ValueSet](https://build.fhir.org/valueset.html), [ConceptMap](https://build.fhir.org/conceptmap.html), and [NamingSystem](https://build.fhir.org/namingsystem.html). We'll dedicate one post to each one of these. For now we'll give a brief overview of `CodeSystem` and `ValueSet`.

### CodeSystem

CodeSystem is arguably the most important resource in FHIR Terminology. It's used to describe a code system, its attributes, and contents. In its most common form, it contains a list of concepts, each concept with its code, designations, and properties.

#### Examples

{% tabs %}
{% tab title="AdministrativeGender" %}
```yaml
resourceType: CodeSystem
url: http://hl7.org/fhir/administrative-gender
version: 5.0.0
content: complete
status: active
concept:
  - code: male
    display: Male
  - code: female
    display: Female
  - code: other
    display: Other
  - code: unknown
    display: Unknown
```
{% endtab %}
{% tab title="ES Translation" %}
```yaml
resourceType: CodeSystem
content: supplement
supplements: http://hl7.org/fhir/administrative-gender
concept:
- code: male
  designation:
  - language: es
    value: Masculino
- code: female
  designation:
  - language: es
    value: Femenino
- code: other
  designation:
  - language: es
    value: Otro
- code: unknown
  designation:
  - language: es
    value: Desconocido
```
{% endtab %}
{% tab title="RxNorm fragment" %}
```yaml
resourceType: CodeSystem
url: http://www.nlm.nih.gov/research/umls/rxnorm
version: '03022026'
publisher: National Library of Medicine
content: complete
filter:
- code: concept
  operator: [is-a, generalizes, descendent-of]
  value: comma-separated list of concept codes for direct equality testing
- code: NDC
  operator: [=,in, exists]
  value: NDC code
property:
- {code: TTY, type: string}
- {code: NDC, type: string}
- {code: RXN_STRENGTH, type: string}
- {code: dose_form_of, type: code}
- {code: has_ingredient, type: code}
- {code: has_tradename, type: code}
# ...
concept:
- code: '5640'
  display: ibuprofen
  property:
  - code: TTY
    valueString: IN
  - code: has_tradename
    valueCode: '1100067'
  - code: ingredient_of
    valueCode: '1152223'
  - code: part_of
    valueCode: '821036'
  # ...
# ...
```
{% endtab %}
{% tab title="ICD-10-CM fragment" %}
```yaml
resourceType: CodeSystem
url: http://hl7.org/fhir/sid/icd-10-cm
version: '2025'
content: complete
property:
- description: Indicator [...] transactions ("billable code")
  type: integer
  code: valid
filter:
- description: Identify valid billable codes.
  value: 0 = "header" – not valid [...]
  code: valid
  operator: ["="]
valueSet: http://hl7.org/fhir/sid/icd-10-cm/vs
concept:
- code: Chapter-1
  display: Certain infectious and parasitic diseases (A00-B99)
  concept:
  - code: Section-A00-A09
    display: Intestinal infectious diseases (A00-A09)
    concept:
    - code: A00
      display: Cholera
      concept:
      - code: A00.0
        display: Cholera due to Vibrio cholerae 01, biovar cholerae
        property:
        - code: valid
          valueInteger: 1
# ...
```
{% endtab %}
{% endtabs %}

### ValueSet

A ValueSet resource specifies a set of codes drawn from one or more code systems.

In our experience, this is one of the most misunderstood resources in FHIR terminology. One possible reason is that the difference between code systems and value sets is not always clear to implementers. It doesn't help that, in FHIR core, most CodeSystem resources have an equivalent ValueSet that includes all their codes. For example: `http://hl7.org/fhir/administrative-gender` and `http://hl7.org/fhir/ValueSet/administrative-gender`.

The main distinction is that code systems contain the definition of the concepts, their origin, while value sets _select_ codes from the code systems. Thus, a value set can have a subset of a code system, or even mix multiple.

For example, RxNorm is a drug terminology that includes concepts for brand names, ingredients, dose forms, etc. We can define a value set of the ingredients in RxNorm. The concepts included would still be RxNorm concepts, but we would only have ingredients.

Another common use case for value sets is adding a null option. Let's say we're using ISO 3166 (Country codes) in a form. It correctly shows every country and their codes, but there's no "Unknown" country code. We can define a value set that _includes_ all codes from ISO 3166 and the `UNK` concept from [NullFlavor](http://terminology.hl7.org/CodeSystem/v3-NullFlavor).

#### Examples

{% tabs %}
{% tab title="RxNorm Ingredients" %}
```yaml
resourceType: ValueSet
url: http://example.org/rxnorm-ingredients
title: RxNorm Ingredients
compose:
  include:
  - system: http://www.nlm.nih.gov/research/umls/rxnorm
    filter:
    - property: TTY
      op: =
      value: IN
```
{% endtab %}
{% tab title="LOINC Cholesterol" %}
```yaml
resourceType: ValueSet
url: http://hl7.org/fhir/ValueSet/example
title: LOINC Codes for Cholesterol in Serum/Plasma
compose:
  include:
  - system: http://loinc.org
    concept:
    - code: 14647-2
      display: Cholesterol [Moles/Volume]
    - code: 2093-3
      display: Cholesterol [Mass/Volume]
    - code: 35200-5
      display: Cholesterol [Mass Or Moles/Volume]
    - code: 9342-7
      display: Cholesterol [Percentile]
```
{% endtab %}
{% tab title="AdministrativeGender" %}
```yaml
resourceType: ValueSet
url: http://hl7.org/fhir/ValueSet/administrative-gender
status: active
compose:
  include:
  - system: http://hl7.org/fhir/administrative-gender
```
{% endtab %}
{% tab title="Yes / No / Dont Know" %}
```yaml
resourceType: ValueSet
url: http://hl7.org/fhir/ValueSet/yesnodontknow
name: YesNoDontKnow
compose:
  include:
  - valueSet: ["http://terminology.hl7.org/ValueSet/v2-0136"]
  - system: http://terminology.hl7.org/CodeSystem/data-absent-reason
    concept:
    - code: asked-unknown
      display: Don't know
```
{% endtab %}
{% endtabs %}

## Operations

The main way of interacting with the terminology module and its resources is via operations. The R6 spec defines 8 terminology-specific operations. Throughout this series we'll look at most of them in detail. In this post we'll introduce `$expand`, the one powering most of our examples above.

### ValueSet/$expand

Given a value set, returns the list of codes that it defines. This operation is ideal for populating UI elements: dropdowns, search fields, code pickers, etc. Besides the value set, you can specify a text search filter, pagination, what properties and designations to bring back, what language to return, among other parameters.

Take our first example: a typeahead search field on SNOMED's clinical findings. Let's say we want to search for "diabetes mellitus" and get 10 results. Here's what a request could look like:

{% tabs %}
{% tab title="hurl" %}
```hurl
GET https://tx-sandbox.health-samurai.io/fhir/ValueSet/$expand
[Query]
url: http://snomed.info/sct?fhir_vs=isa/404684003
count: 10
filter: diabetes mellitus
```
{% endtab %}
{% tab title="curl" %}
```bash
curl -G 'https://tx-sandbox.health-samurai.io/fhir/ValueSet/$expand' \
  --data-urlencode 'url=http://snomed.info/sct?fhir_vs=isa/404684003' \
  --data-urlencode 'count=10' \
  --data-urlencode 'filter=diabetes mellitus'
```
{% endtab %}
{% endtabs %}

<div class="narrow">

| Param | Value | Explanation |
| --- | --- | --- |
| `url` | `http://snomed.info/sct?fhir_vs=isa/404684003` | Implicit URL for SNOMED's clinical finding concepts[^5] |
| `count` | `10` | How many concepts to match |
| `filter` | `diabetes mellitus` | Text to match |

</div>

We can also provide a value set inline on the request, this allows us to craft queries on a code system via properties. For example, let's say we want to get all brand medications that have both caffeine and acetaminophen as ingredients.

{% tabs %}
{% tab title="hurl" %}
```hurl
POST https://tx-sandbox.health-samurai.io/fhir/ValueSet/$expand
Content-Type: application/json
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "valueSet",
      "resource": {
        "resourceType": "ValueSet",
        "status": "active",
        "compose": {
          "include": [
            {
              "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
              "filter": [
                { "property": "TTY", "op": "=", "value": "BN" },
                { "property": "tradename_of", "op": "=", "value": "CUI:161" },
                { "property": "tradename_of", "op": "=", "value": "CUI:1886" }
              ]
            }
          ]
        }
      }
    }
  ]
}
```
{% endtab %}
{% tab title="curl" %}
```bash
curl -X POST 'https://tx-sandbox.health-samurai.io/fhir/ValueSet/$expand' \
  -H 'Content-Type: application/json' \
  -d '{
    "resourceType": "Parameters",
    "parameter": [
      {
        "name": "valueSet",
        "resource": {
          "resourceType": "ValueSet",
          "status": "active",
          "compose": {
            "include": [
              {
                "system": "http://www.nlm.nih.gov/research/umls/rxnorm",
                "filter": [
                  { "property": "TTY", "op": "=", "value": "BN" },
                  { "property": "tradename_of", "op": "=", "value": "CUI:161" },
                  { "property": "tradename_of", "op": "=", "value": "CUI:1886" }
                ]
              }
            ]
          }
        }
      }
    ]
  }'
```
{% endtab %}
{% endtabs %}

<div class="narrow">

| Filter | Value | Explanation |
| --- | --- | --- |
| `TTY` | `BN` | Only brand names |
| `tradename_of` | `CUI:161` | Contains acetaminophen |
| `tradename_of` | `CUI:1886` | Contains caffeine |

</div>

## Next

In our next post we'll look at canonicals. How are terminology resources identified, what conventions are used, how versioning and resolution works.

- Previous: [Introduction to FHIR Terminology](/articles/introduction-to-fhir-terminology)
- Next: Canonicals — _coming soon_

<!-- footnotes -->

[^1]: The most efficient way of implementing some of these examples is using [batching](https://build.fhir.org/http.html#transaction), we're keeping the requests isolated for easier inspection.

[^2]: We'll dive deeper into how these URLs work in a future post about canonicals.

[^3]: The spec actually defines 4 datatypes (and 3 additional ones). But, `CodeableReference` is defined in terms of `CodeableConcept` and the additional ones are special cases. That's why we focus on the first three. See https://build.fhir.org/terminologies.html. 

[^4]: In a future post we'll look at a tutorial for setting up a terminology server to power a healthcare app from scratch.

[^5]: See https://terminology.hl7.org/en/SNOMEDCT.html for how to use SNOMED with FHIR Terminology. We'll dedicate a future post to SNOMED CT.
