Working with FHIR References: Literal, Logical, Contained and Bundle References

How FHIR resources point at each other, how to resolve each kind of reference inside a Bundle and against a server, and the reference bugs that break integrations.

By the FHIR Toolbox team, Omindra Labs · 6 min read ·

Checked against the official specifications listed under Standards and references at the end of this article.

Almost every useful FHIR resource points at another one. An Observation is *about* a Patient, was recorded *during* an Encounter and was *performed by* a Practitioner. Those links are Reference elements, and most real-world integration bugs — orphaned lab results, duplicate patients after a load, "resource not found" on a perfectly good Bundle — come from references that were written or resolved incorrectly.

This guide covers the four shapes a reference can take, the exact rules for resolving them inside a Bundle, and the search features that let a server follow them for you.

The Reference data type

A Reference has four elements. All are optional, but at least one must be present:

ElementTypePurpose
referencestringA literal URL to the target: relative, absolute, or a #fragment
typeuriThe target's resource type, e.g. Patient (added in R4)
identifierIdentifierA logical reference by business identifier, such as an MRN
displaystringHuman-readable text for the target
"subject": {
  "reference": "Patient/example",
  "type": "Patient",
  "display": "Amy Shaw"
}

The element definition also restricts which resource types are allowed. In R4, Observation.subject is Reference(Patient | Group | Device | Location) (R5 widens the list), while Encounter.subject only allows Patient or Group. The Resource Inspector shows the allowed targets for any element. Pointing at a type that isn't listed is a validation error even if the target exists.

1. Literal references

The common case. reference holds a URL that can be followed:

  • Relative: "Patient/123" — resolved against the base URL of the server (or Bundle entry) the resource came from.
  • Absolute: "https://fhir.example.org/r4/Patient/123" — points at a specific server, which may not be the one you are talking to.
  • Version-specific: "Patient/123/_history/4" — pins one version. Use it when the meaning would change if the target changed, for example a Provenance record.

Relative references are preferred inside a single server because they survive base-URL changes (moving from staging to production, or a reverse proxy rewriting hosts).

2. Logical references

Sometimes you know *who* the target is but not its URL on this server — typically when sending data to a system that assigns its own ids. Use identifier:

"subject": {
  "type": "Patient",
  "identifier": {
    "system": "http://hospital.example.org/mrn",
    "value": "00123456"
  }
}

A logical reference cannot be "followed" — the receiver has to look the identifier up (for example GET Patient?identifier=http://hospital.example.org/mrn|00123456). Always include system; an MRN value without its namespace is ambiguous across facilities. Many servers don't resolve logical references at all, so check the receiver's documentation before relying on them.

3. Contained resources

A contained resource lives *inside* another resource and has no independent existence. You reference it with a fragment:

{
  "resourceType": "MedicationRequest",
  "id": "rx1",
  "contained": [
    {
      "resourceType": "Medication",
      "id": "med1",
      "code": { "text": "Amoxicillin 250 mg/5 mL oral suspension" }
    }
  ],
  "status": "active",
  "intent": "order",
  "medicationReference": { "reference": "#med1" },
  "subject": { "reference": "Patient/example" }
}

Rules worth knowing:

  • A contained resource must be referenced from somewhere in its container (or reference the container with "#"). An unreferenced contained resource is a validation error.
  • Contained resources can't contain other resources, and their id is only unique within the container.
  • They can't be searched, updated or referenced from outside.

Use contained only when the target genuinely has no identity of its own — a medication described as free text, or a practitioner known only by name. If the thing will be referenced again, give it its own resource.

4. References inside a Bundle

Bundles add one more element: every entry has a fullUrl, the absolute identity of that entry's resource. References between entries are resolved by matching against fullUrl.

The specification's resolution rules, simplified:

  1. If the reference is an absolute URL (including urn:uuid:), look for an entry whose fullUrl matches it exactly.
  2. If the reference is relative (Patient/123) and the referencing entry's fullUrl is a RESTful URL, build an absolute URL from that entry's base (https://fhir.example.org/r4/ + Patient/123) and look for a matching fullUrl.
  3. If nothing matches, the reference points outside the Bundle; a client may try to resolve it against the server.

urn:uuid references in transactions

When you create several related resources in one transaction, none of them have server ids yet. Give each a temporary urn:uuid and use those in references:

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "urn:uuid:3f1c1d2e-8f0a-4c6e-9b0e-5d3b2a1c7e10",
      "resource": { "resourceType": "Patient", "name": [{ "family": "Shaw", "given": ["Amy"] }] },
      "request": { "method": "POST", "url": "Patient" }
    },
    {
      "fullUrl": "urn:uuid:9a7b6c5d-4e3f-4a2b-8c1d-0e9f8a7b6c5d",
      "resource": {
        "resourceType": "Observation",
        "status": "final",
        "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] },
        "subject": { "reference": "urn:uuid:3f1c1d2e-8f0a-4c6e-9b0e-5d3b2a1c7e10" },
        "valueQuantity": { "value": 72, "unit": "beats/minute", "system": "http://unitsofmeasure.org", "code": "/min" }
      },
      "request": { "method": "POST", "url": "Observation" }
    }
  ]
}

The server assigns real ids and rewrites every urn:uuid reference to the new Patient/{id} before storing. This only happens in transaction Bundles. In a batch, entries are processed independently and must not depend on each other, so a urn:uuid reference to another batch entry will fail.

You rarely need to fetch referenced resources one by one. FHIR search can follow references for you:

  • _include returns referenced resources alongside matches: GET Observation?code=http://loinc.org|8867-4&_include=Observation:patient returns the Observations plus their Patients in one Bundle (included entries have search.mode = include).
  • _revinclude goes the other way: GET Patient?name=shaw&_revinclude=Observation:patient returns patients and every Observation that points at them.
  • Chaining filters on the target's fields: GET Observation?subject:Patient.name=shaw.
  • Reverse chaining (_has) filters on resources pointing back: GET Patient?_has:Observation:patient:code=http://loinc.org|8867-4 finds patients who have a heart-rate observation.

See Mastering FHIR Search for the full syntax.

Reference, canonical and CodeableReference

Two related types are easy to confuse with Reference:

  • canonical is a plain URI string pointing at a *definitional* resource by its canonical URL, optionally with a version: "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1". It is used for profiles, value sets and questionnaires, and is resolved by URL, not by server id.
  • CodeableReference (new in R5) holds either a concept, a reference, or both. R5's MedicationRequest.medication is a CodeableReference(Medication), which replaces R4's medicationCodeableConcept / medicationReference choice.

Common reference bugs

  • Missing resource type: "reference": "123" is not a valid relative reference. It must be Patient/123.
  • Using display as the only link: a display-only reference is legal but can't be resolved; downstream systems will treat the data as unattached.
  • Absolute references to localhost: test data generated against http://localhost:8080/fhir/ leaks into production Bundles and points nowhere.
  • Duplicate fullUrl values in a Bundle make resolution ambiguous. The spec forbids them (except for different versions in a history Bundle).
  • Assuming references were resolved: a successful POST doesn't mean the target exists. Many servers accept dangling references unless referential integrity is turned on.

Try it

Paste a Bundle into the FHIR Reference Resolver to see every reference, which entry it resolves to, and which ones point outside the Bundle. The resolver matches references by fullUrl and by ResourceType/id, and runs entirely in your browser. To see where each reference sits in the structure, use the tree visualizer.

Standards and references

FHIR Toolbox is a free collection of HL7 FHIR tools by Omindra Labs. The tools process your data in your browser; it is not uploaded for normal tool operations.