This is the second post in the Introduction to FHIR Terminology Series. 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 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 responses1.
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 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:
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 URL2 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 3 main data types to represent these codes3: code, Coding, and CodeableConcept.
code: The instance represents the code only. The system is implicit.Coding: The datatype contains a code and a system that identifies where the definition of the code comes from.CodeableConcept: A type that represents a concept by plain text and/or one or morecodingelements.
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. 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.

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
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
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
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
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 loaded4 into (or from) a terminology server.
The Terminology Module defines four main terminology resources: CodeSystem, ValueSet, ConceptMap, and NamingSystem. 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
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
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
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'
# ...
# ...
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
# ...
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.
Examples
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
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]
resourceType: ValueSet
url: http://hl7.org/fhir/ValueSet/administrative-gender
status: active
compose:
include:
- system: http://hl7.org/fhir/administrative-gender
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
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:
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
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'
| Param | Value | Explanation |
|---|---|---|
url | http://snomed.info/sct?fhir_vs=isa/404684003 | Implicit URL for SNOMED's clinical finding concepts5 |
count | 10 | How many concepts to match |
filter | diabetes mellitus | Text to match |
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.
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" }
]
}
]
}
}
}
]
}
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" }
]
}
]
}
}
}
]
}'
| Filter | Value | Explanation |
|---|---|---|
TTY | BN | Only brand names |
tradename_of | CUI:161 | Contains acetaminophen |
tradename_of | CUI:1886 | Contains caffeine |
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
- Next: Canonicals — coming soon
Footnotes
-
The most efficient way of implementing some of these examples is using batching, we're keeping the requests isolated for easier inspection. ↩
-
We'll dive deeper into how these URLs work in a future post about canonicals. ↩
-
The spec actually defines 4 datatypes (and 3 additional ones). But,
CodeableReferenceis defined in terms ofCodeableConceptand the additional ones are special cases. That's why we focus on the first three. See https://build.fhir.org/terminologies.html. ↩ -
In a future post we'll look at a tutorial for setting up a terminology server to power a healthcare app from scratch. ↩
-
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. ↩





