Observation
Overview
Observation is the most used resource in clinical FHIR. Lab results, vital signs, social history, clinical findings, screening assessment scores, SDOH data, and patient-reported outcomes all map to Observation. Its breadth is intentional — it is a general-purpose clinical measurement resource — but that breadth creates real implementation risk.
The single most consequential decision when creating an Observation is the category field. Category determines which profile validates the resource. A lab result without category: laboratory will not be found by queries that search for lab results. A vital sign with the wrong category will not conform to the US Core Vital Signs profile family. Getting category right is not optional.
Observation reached normative status in R4. The core structure is stable. R5 adds some improvements (notably bodyStructure and triggeredBy), but the R4 model is what the industry is built on.
Category Codes and the Profiles They Select
| Category code | System | Profile applies |
|---|---|---|
laboratory | observation-category | US Core Laboratory Result Observation |
vital-signs | observation-category | US Core Vital Signs (and the full profile family) |
social-history | observation-category | US Core Smoking Status, US Core SDOH |
survey | observation-category | PRO instruments, screening assessments |
exam | observation-category | Clinical examination findings |
imaging | observation-category | Imaging-related measurements |
procedure | observation-category | Procedure-related findings |
activity | observation-category | Physical activity, exercise |
The system URI for all of these is http://terminology.hl7.org/CodeSystem/observation-category.
Category is not a free-text field. It drives profile selection by validators, query filtering by client systems, and display routing in EHR portals. A validator checking a US Core Laboratory Result profile will look for category containing a coding with code laboratory in the above system. If it’s absent or uses a different system URI, validation fails.
US Core allows multiple categories on a single Observation. An SDOH Observation may have both social-history and a SDOH-specific category code from a second system.
Key Elements
status
The lifecycle of a result:
registered— ordered, not yet resultedpreliminary— partial or preliminary result availablefinal— completed, not expected to changeamended— result was changed after being finalcorrected— error was corrected after being finalcancelled— never completed; no resultentered-in-error— the result record itself was entered in error
status is required. The most common mistake is simply omitting it. A receiving system that doesn’t see status: final cannot know whether to act on the result.
For amendment and correction workflows, FHIR does not require creating a new Observation — the original resource is updated with status: amended or status: corrected. But many implementations create a new Observation and mark the old one entered-in-error. Know which pattern your trading partners use.
code
What was measured. LOINC is the standard for laboratory tests and vital signs. SNOMED CT is used for clinical findings and examination results.
code is the identity of the measurement. It is distinct from the value: code says “this is a serum potassium measurement”; the value[x] says “the result is 3.8 mEq/L.”
For laboratory observations, there is an additional complication: the LOINC code for the ordered test and the LOINC code for the measured result may differ. A test order for “Basic Metabolic Panel” (LOINC 51990-0) produces individual component results each with their own LOINC codes. The Observation’s code should be the code for the measured result, not the order.
subject
Typically a reference to Patient. Can also be Group, Device, or Location for non-clinical observations. US Core profiles require subject to reference a US Core Patient.
effective[x]
When the measurement was made. The choice of variant matters:
effectiveDateTime— a single point in time. Use for discrete test results, most lab values.effectivePeriod— a start and end. Use for measurements collected over a period (24-hour urine, continuous monitor average).effectiveTiming— recurring observations with a schedule. Rare.effectiveInstant— millisecond precision. Use for streaming data, high-frequency monitoring.
If the measurement was collected at a specific time, use effectiveDateTime. If it represents a collection window, use effectivePeriod. The wrong choice creates downstream confusion about when the result applies.
issued
The date/time the result was issued into the system (transcription or transmission time). Distinct from effective[x]. effective is when the measurement was taken; issued is when the system recorded it. Both are useful for audit and sync purposes.
performer
Who performed the observation. References to Practitioner, PractitionerRole, Organization, CareTeam, Patient, or RelatedPerson. For lab results, this is typically the laboratory Organization. For vital signs, the nursing staff or device.
value[x] — The Result
The value[x] element is polymorphic. The actual field name changes based on the data type used:
| Field name | Use for |
|---|---|
valueQuantity | Numeric results with a unit (lab values, vitals) |
valueCodeableConcept | Coded results (positive/negative, blood type) |
valueString | Free-text results — use sparingly, prefer coded |
valueBoolean | True/false findings |
valueInteger | Integer counts |
valuePeriod | Time window results |
valueRange | Results expressed as a range |
valueRatio | Ratio results (titers) |
valueSampledData | Waveform or sampled data (ECG, SpO2 stream) |
valueQuantity is the most common for laboratory and vital sign results. It must include UCUM as the unit system when the unit is a standard UCUM unit:
"valueQuantity": {
"value": 3.8,
"unit": "mEq/L",
"system": "http://unitsofmeasure.org",
"code": "meq/L"
}
unit is the human-readable display string. code is the UCUM code. system must be http://unitsofmeasure.org for US Core conformance.
valueCodeableConcept is correct when the result is a code, not a number. Blood type, organism identification, qualitative results (positive/negative/indeterminate) — these are coded results. Using valueString for “Positive” when there’s a coded value for positivity is a common mistake.
dataAbsentReason
When a result was expected but is absent, dataAbsentReason explains why. Do not omit value[x] without providing dataAbsentReason. The two elements are mutually exclusive — you either have a value or an absence reason, not both.
Common codes from http://terminology.hl7.org/CodeSystem/data-absent-reason:
unknown— the value is not knownasked-unknown— the patient was asked and did not knowrefused— the patient refused to provide the resultnot-performed— the measurement was not performederror— the measurement failed (instrument error, etc.)masked— the value exists but is restricted
A receiving system that sees no value[x] and no dataAbsentReason has no way to distinguish “the value is missing from the data feed” from “the measurement genuinely wasn’t taken.”
interpretation
The lab’s qualitative flag on the result:
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "H",
"display": "High"
}
]
}
]
Common codes: N (normal), H (high), L (low), HH (critically high), LL (critically low), A (abnormal), AA (critically abnormal).
interpretation is the lab’s assessment relative to reference ranges. It is not clinical significance — that is a physician judgment. A critically high potassium (HH) is flagged by the lab algorithm; whether it’s clinically actionable depends on patient context.
referenceRange
Normal ranges for the measured value. Can include both a numeric range and textual description:
"referenceRange": [
{
"low": { "value": 3.5, "unit": "mEq/L", "system": "http://unitsofmeasure.org", "code": "meq/L" },
"high": { "value": 5.0, "unit": "mEq/L", "system": "http://unitsofmeasure.org", "code": "meq/L" },
"appliesTo": [
{
"coding": [
{ "system": "http://terminology.hl7.org/CodeSystem/referencerange-meaning", "code": "normal" }
]
}
],
"text": "3.5-5.0 mEq/L"
}
]
appliesTo can specify that the range applies to a specific age group, gender, or population. Reference ranges are informational — they are not used for validation, only for display and clinical interpretation.
component vs hasMember
This distinction is the most commonly misunderstood structural choice in Observation. Getting it wrong causes downstream query failures.
component: Use when an observation produces multiple values that are not independently meaningful — they are parts of a single measurement that only make sense together.
The canonical example is blood pressure: systolic and diastolic are components of one blood pressure measurement. Neither is meaningful in isolation without the other. They are not separate results; they are facets of a single observation event.
hasMember: Use when a panel or grouper observation points to independently meaningful member observations. Each member is a complete, standalone Observation resource that can be searched, retrieved, and interpreted on its own.
A CBC with differential is a hasMember panel. The white blood cell count, neutrophil count, lymphocyte count, and hemoglobin are all independently meaningful lab results that clinicians query and alert on individually. The panel Observation groups them; the member Observations are the actual results.
Why it matters: A query for Observation?code=8480-6 (systolic blood pressure) will find systolic blood pressure when it is a component on a blood pressure Observation. It will also find it when it is a standalone Observation. But it will NOT automatically traverse the hasMember reference chain to find components of a panel. Systems that store CBC results as components of a single Observation rather than as hasMember Observations will fail blood count queries from systems that expect individual Observations.
JSON Examples
Laboratory Observation
{
"resourceType": "Observation",
"id": "obs-potassium-001",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory",
"display": "Laboratory"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "2823-3",
"display": "Potassium [Moles/volume] in Serum or Plasma"
}
],
"text": "Potassium"
},
"subject": {
"reference": "Patient/patient-001",
"type": "Patient"
},
"effectiveDateTime": "2024-03-15T09:22:00Z",
"issued": "2024-03-15T10:45:00Z",
"performer": [
{
"reference": "Organization/lab-org-001",
"display": "Central Hospital Laboratory"
}
],
"valueQuantity": {
"value": 3.3,
"unit": "mEq/L",
"system": "http://unitsofmeasure.org",
"code": "meq/L"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "L",
"display": "Low"
}
]
}
],
"referenceRange": [
{
"low": { "value": 3.5, "unit": "mEq/L", "system": "http://unitsofmeasure.org", "code": "meq/L" },
"high": { "value": 5.0, "unit": "mEq/L", "system": "http://unitsofmeasure.org", "code": "meq/L" },
"text": "3.5-5.0 mEq/L"
}
]
}
Blood Pressure (component — two values, one observation event)
{
"resourceType": "Observation",
"id": "obs-bp-001",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "55284-4",
"display": "Blood pressure systolic and diastolic"
}
],
"text": "Blood pressure"
},
"subject": {
"reference": "Patient/patient-001",
"type": "Patient"
},
"effectiveDateTime": "2024-03-15T08:30:00Z",
"component": [
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8480-6",
"display": "Systolic blood pressure"
}
]
},
"valueQuantity": {
"value": 142,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "H",
"display": "High"
}
]
}
]
},
{
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "8462-4",
"display": "Diastolic blood pressure"
}
]
},
"valueQuantity": {
"value": 88,
"unit": "mmHg",
"system": "http://unitsofmeasure.org",
"code": "mm[Hg]"
},
"interpretation": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code": "N",
"display": "Normal"
}
]
}
]
}
]
}
CBC Panel (hasMember — independently meaningful results grouped together)
{
"resourceType": "Observation",
"id": "obs-cbc-panel",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "58410-2",
"display": "CBC panel - Blood by Automated count"
}
],
"text": "Complete Blood Count"
},
"subject": { "reference": "Patient/patient-001" },
"effectiveDateTime": "2024-03-15T09:22:00Z",
"hasMember": [
{ "reference": "Observation/obs-wbc-001", "display": "White Blood Cell Count" },
{ "reference": "Observation/obs-rbc-001", "display": "Red Blood Cell Count" },
{ "reference": "Observation/obs-hgb-001", "display": "Hemoglobin" },
{ "reference": "Observation/obs-hct-001", "display": "Hematocrit" },
{ "reference": "Observation/obs-plt-001", "display": "Platelet Count" }
]
}
Each hasMember reference points to a complete, standalone Observation with its own code, valueQuantity, referenceRange, and interpretation. Clients can retrieve individual results without fetching the panel grouper.
Search Parameters
| Parameter | Type | Finds |
|---|---|---|
code | token | Observations with this LOINC or other code |
patient | reference | Observations for this patient |
category | token | Observations with this category code |
date | date | Observations with effective date matching |
status | token | Observations with this status |
performer | reference | Observations performed by this resource |
value-concept | token | Observations with a coded value matching |
value-quantity | quantity | Numeric values matching (supports prefixes: gt, lt, etc.) |
component-code | token | Observations with a component having this code |
component-value-quantity | quantity | Observations with a component value matching |
combo-code-value-quantity | composite | Component or top-level code + quantity value |
Typical laboratory query:
GET [base]/Observation?patient=Patient/123&category=laboratory&date=ge2024-01-01&_sort=-date&_count=50
Systolic blood pressure component query:
GET [base]/Observation?patient=Patient/123&component-code=http://loinc.org|8480-6&component-value-quantity=gt140
US Core Profiles
US Core defines a family of Observation profiles. The relevant profile is determined by the category code and, for vital signs, the specific code value:
| Profile | Category | Code |
|---|---|---|
| US Core Laboratory Result | laboratory | any LOINC result code |
| US Core Vital Signs | vital-signs | panel LOINC (any) |
| US Core Body Temperature | vital-signs | 8310-5 |
| US Core Body Weight | vital-signs | 29463-7 |
| US Core Body Height | vital-signs | 8302-2 |
| US Core Blood Pressure | vital-signs | 55284-4 |
| US Core BMI | vital-signs | 39156-5 |
| US Core Head Circumference | vital-signs | 9843-4 |
| US Core Oxygen Saturation | vital-signs | 59408-5 |
| US Core Respiratory Rate | vital-signs | 9279-1 |
| US Core Heart Rate | vital-signs | 8867-4 |
| US Core Smoking Status | social-history | 72166-2 |
US Core Vital Signs requires UCUM units in valueQuantity. US Core Laboratory Result requires LOINC in code for most tests.
R5 Changes
R5 adds:
bodyStructure: A reference to a BodyStructure resource, replacing the codedbodySiteelement for complex anatomy scenarios.triggeredBy: Describes what triggered this Observation — another Observation, a condition, or a protocol step. Useful for reflex testing chains.instantiates[x]: References the protocol or plan that defined this observation.
For R4-based implementations, these additions are informational. The core element model is unchanged.
Implementation Considerations
LOINC test code vs result code. When a laboratory receives an order, the order may have one LOINC code; the result has another. The Observation code should be the result’s LOINC code — what was measured — not the order code. For panels, the panel LOINC goes on the grouper; individual result LOINCs go on the hasMember Observations.
UCUM is required for valueQuantity.system. US Core profiles require http://unitsofmeasure.org as the system for any numeric quantity. Local unit strings without UCUM codes fail validation and break unit conversion logic in receiving systems.
The “everything is an Observation” trap. Observation is flexible enough to hold almost any clinical datum, but that doesn’t mean it should. A diagnosis is a Condition. A procedure that was performed is a Procedure. A prescription is a MedicationRequest. Shoehorning these into Observations because it’s convenient creates data that downstream systems — which query Condition, Procedure, and MedicationRequest specifically — will never find.
Common Mistakes
Missing status. Observation.status is required (cardinality 1..1) and normative validators will reject its absence. Default to final for completed results; use preliminary for partial results; use entered-in-error for corrections that invalidate the original.
Omitting category. Without category, the resource can’t be correctly routed to profile validation, can’t be found by category-filtered queries, and won’t appear in category-specific clinical views. Always include category with the correct system URI.
valueString for coded results. If the result is positive, negative, or indeterminate, use valueCodeableConcept with a coded value from SNOMED CT or a local CodeSystem. valueString: "Positive" is unvalidatable, untranslatable, and fragile to free-text variation.
component for independently meaningful panel results. Blood pressure components are correct as component. CBC results are not — they belong as hasMember Observations. The test: can this result be clinically queried, alerted on, or trended independently? If yes, it should be its own Observation.
Wrong effective[x] type. Using effectiveDateTime for a 24-hour urine collection truncates the measurement period to a single point. Use effectivePeriod with explicit start and end.