The most common use of transaction bundles in FHIR is to update or create multiple resources in a FHIR server at the same time.
The update should succeed completely or fail completely.
Transaction bundles might be used:
- When a Practitioner makes Observations about a Patient during an Encounter
- When a system receives notification of a scheduled Procedure for a Patient
- When a utility performs a bulk data import into FHIR from another system
Consider this scenario.
A chain of clinics wants to monitor and report on medications prescribed to patients by doctors. Each clinic is isolated and they use many different clinical management systems.
The solution devised is to have each hospital send a daily CSV file to an API endpoint. Not an ideal solution but far from uncommon. The CSV file contains one line per prescription with the following details:
- The patient, with MRN
- The practitioner who wrote the prescription, with accompanying NPI number
- The medication prescribed
The API endpoint reads the file and converts each row into a FHIR transaction bundle containing three connected resources: Patient, Practitioner and MedicationRequest. It POSTs the bundle to a FHIR server where it’s stored and accessed later.
Here’s what the FHIR resources in the bundle look like:
- The Patient, Charlotte Colvin
- The Practitioner, Dr. Henrietta Shawcross
- The MedicationRequest, an iron supplement
Sounds simple, but there are two key questions.
1. What prevents duplicate patient and practitioner resources from being created in the FHIR server?
The same doctor will write many prescriptions for different patients, and the same patient is likely to return to the clinic multiple times and be given different prescriptions.
If the Patient and Practitioner resources are sent to the server for every MedicationRequest, what prevents new Patient resources from being created each time?
2. How is the MedicationRequest resource in FHIR able to connect to the correct Patient and Practitioner resource?
The clinic — with their CSV file — does not know the ID the FHIR server creates for a Patient or for a Practitioner. Yet the bundle it sends must be constructed such that the correct Practitioner and Patient connect up to the correct MedicationRequest each time.
Let’s look at MedicationRequest first.

Its subject element contains a resource reference link to the Patient resource, and its requestor element contains a similar link to the Practitioner.
These references look a little different to what you might see when you GET data from a FHIR server. Usually, the reference will look something like this:
Patient/0ff9e2d6-220d-4d20-877c-4775a0283294
A Patient resource with a specific FHIR server ID that identifies the patient
The prefix urn:uuid tells the FHIR server that the reference is a URN, and that it references a resource inside the same bundle with that specific URN as its fullUrl value.
In the Patient resource below, the fullUrl urn:uuid of the patient matches the reference in the MedicationRequest.

The FHIR server now has enough information to run POST or PUT requests on each resource in the bundle AND ensure that each resource reference is changed to match the server ID of the resource it references.
I say changed, because once a new resource is created in the FHIR server, it is assigned a unique ID by that server. The ID values highlighted in the screenshots are not maintained in the FHIR server — they are transient and only last for the lifetime of the request.
In this way the internal consistency of all resources in the bundle is maintained on the server.
So how are duplicates prevented?
If we POST a Patient to the FHIR server it will create a new Patient. Same for Practitioner, regardless of whether or not a doctor with the same name already exists.
This is where conditional updates come into play.
A conditional update is a PUT request with some query parameters attached. Look at the full Practitioner resource for Dr. Shawcross.

She has an identifier that looks like an NPI number — that’s a unique identification number for healthcare providers in the US.
If the data is correct, no other doctor should have the same NPI number. We can use this to prevent a duplicate Practitioner from being created in the FHIR server by appending it to the request URL for the resource.
“url”: “Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|785436955”
When the FHIR server actions the PUT request, it first runs the query to see if a Practitioner exists with an identifier of system “http://hl7.org/fhir/sid/us-npi” and value “785436955”.
- If it finds one, it updates that resource with any changes
- If it fails to find one, it creates a new resource for Dr. Shawcross
- If it finds more than one, it reports an error and the entire transaction stops
The same with the Patient resource.
In order for conditional update requests to work, the data in the FHIR server and in the bundle needs to be good data. If other sources are updating the FHIR server with bad data, duplicate Patient and Practitioner resources can still creep in.
The MedicationRequest resource is part of a POST request, not a PUT request. Why is this?
Unlike the Patient and the Practitioner, multiple MedicationRequests are expected. The CSV file contains one set of prescription details per line.
Using a transaction bundle is a great way to ensure a flat data structure such as this can be converted into FHIR resources and sent to a FHIR server successfully.
Links
FHIR’s Bundle resource.
Transaction bundles in FHIR.
Conditional PUT requests.
Here’s the bundle if you’d like to POST it to your own server.
---