ediverse Explore the platform

Spotlight PEPPOL BIS Billing 3.0 The EU e-invoicing mandate is here — France Sept 2026, Belgium Jan 2026, Germany 2025.

Bundle — Multi-resource container

The envelope that lets FHIR carry several resources at once: search result, atomic transaction, patient document, technical message.

Purpose of the resource

Bundle answers a simple question: how do you ship ten FHIR resources in a single payload? Whether it's a search result returning fifteen Patients, an atomic transaction that creates a Patient and its related Observations, or an IPS document that consolidates the patient summary, Bundle is the universal envelope.

The discrimination happens through the type field, which takes one of the eight allowed values and dictates the Bundle's semantics (with or without transaction, persistent or transient, paged or not).

The eight Bundle types

TypeUsagePersistenceTransaction semantics
documentFHIR Document (IPS patient summary, CDA-like)Yes — may be storedNo transaction. First entry = Composition.
messageAsynchronous exchange, HL7 v2 styleTransientNo transaction. First entry = MessageHeader.
transactionMulti-resource atomic create / updateTransientAtomic — all entries or none. Server resolves references.
transaction-responseServer response to a transactionTransientList of individual outcomes.
batchMulti-resource independent operationsTransientNon-atomic — each entry succeeds or fails independently.
batch-responseServer response to a batchTransientList of individual outcomes.
historyHistory of a resource or of the serverTransientNo transaction.
searchsetSearch resultTransientIncludes pagination via link.
collectionGeneric bag of resources, free interpretationVariableNo imposed technical semantics.
subscription-notificationNotification from an R5 SubscriptionTransientReal-time push mechanism.

Key fields

FieldTypeCardinalityRole
typecode1..1Mandatory. One of the eight (ten with subscription-notification) types above.
timestampinstant0..1Generation time of the Bundle.
totalunsignedInt0..1Total matches for a searchset (may differ from entry.length in paged mode).
linkBackboneElement[]0..*Pagination links: self, next, previous, first, last.
entryBackboneElement[]0..*List of carried resources. Each entry contains:
entry.fullUrluri0..1Absolute URL of the resource. Critical for reference resolution.
entry.resourceResource0..1The resource itself.
entry.searchBackboneElement0..1For searchset: mode (match, include, outcome) and score.
entry.requestBackboneElement0..1For transaction / batch: HTTP method (POST, PUT, DELETE, PATCH, GET) and URL.
entry.responseBackboneElement0..1For transaction-response / batch-response: status, location, etag, lastModified, outcome.
signatureSignature0..1Digital signature of the Bundle (for document and message types in particular).

Example — transaction

A transaction Bundle that creates a new Patient, updates an existing Patient (idempotent PUT), and creates a body weight Observation linked to the first Patient via a relative urn:uuid: reference:

json bundle-transaction.json
{
  "resourceType": "Bundle",
  "id": "bundle-transaction",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059",
      "resource": {
        "resourceType": "Patient",
        "identifier": [{
          "system": "http:/example.org/fhir/ids",
          "value": "234234"
        }],
        "active": true,
        "name": [{ "family": "Chalmers", "given": ["Peter", "James"] }],
        "gender": "male",
        "birthDate": "1974-12-25"
      },
      "request": {
        "method": "POST",
        "url": "Patient"
      }
    },
    {
      "fullUrl": "http://example.org/fhir/Patient/123",
      "resource": {
        "resourceType": "Patient",
        "id": "123",
        "active": true,
        "name": [{ "family": "Smith", "given": ["John"] }],
        "gender": "male",
        "birthDate": "1980-01-15"
      },
      "request": {
        "method": "PUT",
        "url": "Patient/123"
      }
    },
    {
      "fullUrl": "urn:uuid:79378cb8-8f72-4d33-8b8d-d6c0c5e1e3bb",
      "resource": {
        "resourceType": "Observation",
        "status": "final",
        "code": {
          "coding": [{
            "system": "http://loinc.org",
            "code": "29463-7",
            "display": "Body Weight"
          }]
        },
        "subject": {
          "reference": "urn:uuid:88f151c0-a954-468a-88bd-5ae15c08e059"
        },
        "effectiveDateTime": "2026-05-14T10:30:00Z",
        "valueQuantity": {
          "value": 72.5,
          "unit": "kg",
          "system": "http://unitsofmeasure.org",
          "code": "kg"
        }
      },
      "request": {
        "method": "POST",
        "url": "Observation"
      }
    }
  ]
}

Key takeaways:

  • fullUrl as urn:uuid:: for resources created inside the bundle (no server-side id yet), a temporary UUID is used. The server swaps it for the allocated id and resolves internal references.
  • Internal reference: Observation.subject.reference = "urn:uuid:88f151c0..." points to the Patient that will be created in the same transaction. The server guarantees referential consistency.
  • HTTP method per entry: POST for creations (server allocates id), PUT for idempotent updates (client provides id).
  • Atomicity: if any entry fails, the server rolls back the whole thing. No partial change.

Example — searchset

A searchset Bundle returned by GET /Patient?family=Chalmers with two matches and a next pagination link:

json bundle-searchset.json
{
  "resourceType": "Bundle",
  "id": "searchset-example",
  "type": "searchset",
  "total": 2,
  "link": [
    {
      "relation": "self",
      "url": "https://server/Patient?family=Chalmers"
    },
    {
      "relation": "next",
      "url": "https://server/Patient?family=Chalmers&_page=2"
    }
  ],
  "entry": [
    {
      "fullUrl": "https://server/Patient/example",
      "resource": {
        "resourceType": "Patient",
        "id": "example",
        "name": [{ "family": "Chalmers", "given": ["Peter"] }],
        "gender": "male",
        "birthDate": "1974-12-25"
      },
      "search": {
        "mode": "match",
        "score": 1.0
      }
    },
    {
      "fullUrl": "https://server/Patient/example2",
      "resource": {
        "resourceType": "Patient",
        "id": "example2",
        "name": [{ "family": "Chalmers", "given": ["Sarah"] }],
        "gender": "female",
        "birthDate": "1981-07-12"
      },
      "search": {
        "mode": "match",
        "score": 0.95
      }
    }
  ]
}
  • total: two matches (may be omitted if the server is in count-disabled mode).
  • link[self]: the original request.
  • link[next]: next page in the result set. The client follows these links to paginate.
  • entry.search.mode: match = resource matching the criteria; include = resource brought in by _include but not part of the original criteria.
  • entry.search.score: match relevance (0..1), only when the server supports semantic ranking.

Transaction vs batch

The two types transaction and batch share the same structure but carry different semantics:

Aspecttransactionbatch
AtomicityYes — all or nothingNo — each entry independent
Reference resolutionServer resolves urn:uuid: and circular references across entriesNo resolution — each entry is an isolated request
Execution orderServer-side deterministic (DELETE, POST, PUT/PATCH, GET, HEAD)Entry order
Use caseBuilding a complete patient record, posting a genetic test with its observationsIndependent bulk update: refresh 50 unrelated Observations
PerformanceMore expensive server-side (lock + rollback)Cheaper, parallelisable

Common pitfalls

  • Missing or non-URN fullUrl: in a transaction, a missing fullUrl makes internal reference resolution impossible. Always supply one, either as urn:uuid:<UUID> or as an absolute URL.
  • Absolute reference in a transaction: if the Observation references the Patient via "reference": "http://server/Patient/123" while the Patient is being created in the same bundle without that id, the transaction fails.
  • Confusing searchset and collection: some older servers return collection instead of searchset — the client must handle both. Do not confuse the two on the creation side.
  • Bundle > 100 entries: the spec sets no hard limit, but most servers cap it (HAPI: 1000 by default, Epic R5: 100). Pagination becomes mandatory beyond.
  • Unsigned document Bundle: an IPS document without a signature may be rejected by certain national authorities (FR, NL, UK). Always include it for persistent documents.
  • type=transaction POSTed to /Patient: a transaction Bundle is POSTed to the server root (POST /), not to an individual resource URL.
  • Composition — mandatory first entry of a document Bundle.
  • MessageHeader — mandatory first entry of a message Bundle.
  • OperationOutcome — carries errors and warnings in transaction / batch responses.
  • Subscription and SubscriptionStatus — fire subscription-notification Bundles.
  • Parameters — alternative to Bundle for operation parameters ($everything, $lastn…).

See also: Patient — the foundational demographic resource, the FHIR R5 hub, and the functional v2 equivalent of a multi-ORM Bundle: ORM^O01.