---
{
  "title": "Unbound Bindings",
  "description": "How FHIR R5 additional bindings solve the limitations of the classic one-binding-per-element model with layered terminology constraints.",
  "date": "2026-03-24",
  "author": "Nikolai Ryzhikov",
  "reading-time": "14 min read",
  "tags": ["FHIR Standard", "FHIR Profiling", "Terminology"],
  "tldr": "Additional bindings — introduced in FHIR R5 — let you attach multiple terminology constraints to a single element, each with its own purpose: require specific codes for new data, suggest a starter subset, provide UI hints, or enforce jurisdiction-specific rules. They layer on top of the main binding and never replace it."
}
---

## One Element, One Binding — and Its Limits

Since FHIR DSTU2, every coded element has had exactly **one binding** — a rule that ties the element to a set of allowed codes (a ValueSet) with a particular strength. The strength says how strictly the codes must be followed: `required` means you SHALL use a code from this set; `extensible` means use one if it applies to your concept, otherwise you may use other codes; `preferred` and `example` are progressively looser suggestions. For simple elements like `Patient.gender` (bound to [AdministrativeGender](http://hl7.org/fhir/R5/valueset-administrative-gender.html) with `required` strength), this model works perfectly.

But as FHIR adoption grew — across jurisdictions, clinical domains, and regulatory regimes — the one-binding model hit its limits. Profile authors (people who write StructureDefinitions — sets of constraints on top of base FHIR resources) kept running into problems that a single ValueSet couldn't express.

## The Extensible+Max Saga

The first workaround was the **Extensible+Max** pattern. US Core needed a way to say: "use codes from this small ValueSet, but if you must extend, stay within this larger boundary." The solution was the [`maxValueSet` extension](https://hl7.org/fhir/R4/extension-elementdefinition-maxvalueset.html), introduced in [STU3](http://hl7.org/fhir/STU3/extension-elementdefinition-maxvalueset.html) and refined in R4.

It immediately caused confusion. The spec described it as *"a 'required' binding over the top of the extensible binding"* — but no one agreed on what that meant in practice. The core tension, laid bare in a [182-message thread on chat.fhir.org](https://chat.fhir.org/#narrow/stream/argonaut/topic/Extensible%2BMax): Argonaut needed "use this code or free text" for ONC compliance — hospitals had free-text problems that were "literally a string in the local system" (Jenni Syed). But no combination of binding strengths could express that. Josh Mandel pointed out that US Core Immunization bound `vaccineCode` to CVX with *both* extensible and max — to the same ValueSet: "From an interoperability perspective I can't understand why choosing extensible and maximum is better than just choosing extensible."

Meanwhile, a parallel [`minValueSet` extension](https://hl7.org/fhir/R4/extension-elementdefinition-minvalueset.html) tried to set a floor for extensible bindings — if a code from this set applies, you must use it. But it conflated sender constraints with receiver obligations, and validators couldn't enforce it.

Both extensions could only express two narrow use cases, and even those were poorly understood.

## The "I Want Multiple Bindings" Problem

At the same time, [IG](/blog/how-to-create-a-fhir-implementation-guide) authors kept asking for **multiple bindings** on a single element. The most common driver was [IPS (International Patient Summary)](http://hl7.org/fhir/uv/ips/), which needed to express a choice of terminology bindings on a CodeableConcept — different code systems for different jurisdictions. The only tool available was **slicing** — FHIR's mechanism for saying "this repeating element must have entries matching specific patterns." Applied to `CodeableConcept.coding`, slicing let you require one Coding from SNOMED and another from ICD-10. But it was complex to author, hard for implementers to understand, and verbose in profile rendering.

As Grahame Grieve [put it](https://chat.fhir.org/#narrow/stream/terminology/topic/Extension%20for%20additional%20bindings): "There's lots of slicing in Implementation Guides because authors want to say 'one of the codes must be from value set X.' It's the most common use of slicing, and you have to do that because a binding applies to all repeats of the code. But slicing is difficult for implementers."

## Grahame's Proposal (February 2022)

In February 2022, Grahame Grieve [proposed](https://chat.fhir.org/#narrow/stream/terminology/topic/Extension%20for%20additional%20bindings) a new extension on `ElementDefinition.binding` — **additional bindings**. The core idea: the main binding stays as the conformance binding (what the validator enforces), but you can attach extra bindings with different **purposes**:

> I would like to define an extension on ElementDefinition.binding that specifies additional bindings. ElementDefinition.binding is the conformance binding: what the validator enforces — and there can only be one. But in addition, designers will be able to specify additional bindings.

His initial purpose list — `conformance`, `required`, `recommended`, `UI`, `starter`, `component`, `minimum`, `maximum` — addressed every gap the community had been working around. The motivation:

> In a few places we use extensible+max and we mostly really want a base value set with a recommended value set. In a few places we use extensible, and we really want a fairly large base value set with a required — this is what legislation wants for new records.

Rob Hausam immediately saw the value for IPS: "I agree that it looks like this can be a replacement for slicing on CodeableConcept.coding... Assuming that's all true, then when can we start using this in IPS?"

Lloyd McKenzie pushed back: "Generally, if rules are different for different contexts, then you have different profiles." But Grahame countered: "It's no service to users to have myriad profiles that differ only by usage context and some bindings."

By April 2022, the IG Publisher had draft rendering support, and Grahame added the `any` flag — "if `any = true` then the binding only applies to one of the repeats, not all of them." By August 2022, IPS was [already using it](http://build.fhir.org/ig/HL7/fhir-ips/StructureDefinition-Immunization-uv-ips.html). The feature was [committed to the R5 ballot](https://chat.fhir.org/#narrow/stream/fhir.2Finfrastructure-wg/topic/Modifications%20to%20ElementDefinition) as `ElementDefinition.binding.additional`, and shipped in R5 (March 2023) as a first-class element — no longer an extension.

## How It Works: Semantics First

Before diving into the purpose catalog, two fundamental rules.

**Rule 1: all bindings are AND.** The main binding and every additional binding apply simultaneously. Lloyd McKenzie [stated it plainly](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Implementiing%20Additional%20Bindings%20for%20each%20purpose%20unclear): "All bindings always apply. End stop." However, not all bindings bite equally:

- Bindings with a `usage` context are **ignored** if you're not in that context
- Guidance purposes (`starter`, `ui`, `component`, `preferred`) carry **no conformance weight**
- `current` bindings only apply to data captured **since declaring compliance** — not to legacy data
- `extensible` bindings have the standard out: you don't have to comply if no codes in the ValueSet apply to your concept

If you want OR semantics ("send a code from ValueSet A **or** ValueSet B"), combine them into a single grouped ValueSet. Additional bindings won't give you OR.

For a [CodeableConcept](http://hl7.org/fhir/R5/datatypes.html#CodeableConcept) — FHIR's datatype that can carry multiple parallel code representations (Codings) of the same concept — multiple required bindings mean you may need **multiple Codings**, one satisfying each binding. This is how "must send SNOMED AND ICD-10" works without slicing.

**Rule 2: the `any` flag changes repeat semantics.** By default, a binding applies to **all** repetitions of an element. Setting `any = true` means the binding is satisfied if **any one** repetition matches. This is the key mechanism that replaces coding slices — and as Lloyd McKenzie [noted](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Implementiing%20Additional%20Bindings%20for%20each%20purpose%20unclear): "using slicing to do 'and' bindings is now considered an anti-pattern and discouraged."

## Structure and Purposes

An [additional binding](http://hl7.org/fhir/R5/elementdefinition-definitions.html#ElementDefinition.binding.additional) never replaces the main binding — it layers on top. Each has:

| Property | Description |
|----------|-------------|
| `purpose` | Why this binding exists (see tables below) |
| `valueSet` | The ValueSet URL |
| `documentation` | Markdown explaining the use in context |
| `shortDoco` | A brief plain-text summary for tabular rendering |
| `usage` | [UsageContext](http://hl7.org/fhir/R5/metadatatypes.html#UsageContext) scoping by jurisdiction, domain, or workflow |
| `any` | If `true`, satisfied when any one repeat matches (default: all must match) |

Here's how it looks in a StructureDefinition (simplified notation):

```yaml
binding:
  strength: extensible
  valueSet: http://hl7.org/fhir/ValueSet/observation-codes
  additional:
    - purpose: starter
      valueSet: http://example.org/ValueSet/common-lab-codes
      documentation: >
        A manageable subset of ~200 common laboratory codes
        for implementers getting started.
    - purpose: current
      valueSet: http://example.org/ValueSet/2026-lab-codes
      documentation: >
        Required for all new observations captured after 2026-01-01.
        Legacy data may use codes outside this set.
```

The purposes have evolved since Grahame's 2022 proposal (some were renamed, `conformance` was dropped). The `purpose` element has a **required** binding — you can only use codes from the [AdditionalBindingPurposeCodes](http://hl7.org/fhir/R5/valueset-additional-binding-purpose.html) ValueSet, no custom purposes allowed. Here are the R5 codes:

### Conformance Purposes

| Purpose | What it means |
|---------|---------------|
| `required` | The element SHALL use a code from this ValueSet — in the specified usage context |
| `extensible` | Use codes from this ValueSet if applicable — in the specified usage context |
| `current` | New records **SHALL** use this ValueSet. Legacy or externally-sourced data may use other codes |

### Guidance Purposes

| Purpose | What it means |
|---------|---------------|
| `minimum` | All conformant applications **must accept** these codes as valid (application obligation, not instance constraint) |
| `candidate` | May substitute the main ValueSet in documented situations (e.g., a jurisdiction defines a replacement) |
| `preferred` | This ValueSet is preferred in a given context (documentation should explain why) |
| `ui` | Suggested codes for **user interface** lookup and selection |
| `starter` | A practical **subset** to start with when the full ValueSet is too large |
| `component` | Documents how a **part** of the main ValueSet is used (e.g., per code system) |

R6 adds `current-extensible` (extensible semantics for new data), `best-practice` (flagged in best-practice validation mode), and `open` (no restrictions, for documentation).

{% hint style="info" %}
The `maximum` purpose exists in R5 but is **deprecated in R6**. It was originally intended to cap extensible bindings, but its semantics were unclear (see the Extensible+Max saga above). Use `required` with a usage context instead.
{% endhint %}

## Across FHIR Versions

| Version | How additional bindings work |
|---|---|
| **R4 and earlier** | Not native. Use the [additional-binding extension](https://hl7.org/fhir/tools/StructureDefinition-additional-binding.html) from the Tooling Extensions IG. The IG Publisher renders them; the validator supports them. The old `maxValueSet` and `minValueSet` extensions still work but are superseded. |
| **R5** | Native as `ElementDefinition.binding.additional`. Full validator support. No `key` element — constraining additional bindings in derived profiles is limited. |
| **R6** | Native + `key` element for constraining across profile layers. New purposes: `current-extensible`, `best-practice`, `open`. `maximum` deprecated. |

For R4 IGs, additional bindings are expressed as an extension on `ElementDefinition.binding`:

```yaml
binding:
  strength: extensible
  valueSet: http://hl7.org/fhir/ValueSet/observation-codes
  extension:
    - url: http://hl7.org/fhir/tools/StructureDefinition/additional-binding
      extension:
        - url: purpose
          valueCode: current
        - url: valueSet
          valueCanonical: http://example.org/ValueSet/2026-codes
        - url: documentation
          valueMarkdown: Required for new records.
```

In R5+, the same is expressed natively — no extension wrapper needed.

## Scenarios

### Multi-Jurisdiction Conformance

An international profile needs different code systems in different countries — SNOMED CT in the US, ICD-10-GM in Germany. Before additional bindings, this meant either separate profiles per jurisdiction or complex slicing. Now you can express it in a single profile using `required` + `any: true` + `usage`:

```yaml
binding:
  strength: extensible
  valueSet: http://hl7.org/fhir/ValueSet/condition-code
  additional:
    - purpose: required
      valueSet: http://example.org/ValueSet/us-snomed-conditions
      any: true
      usage:
        - code: { code: US, system: urn:iso:std:iso:3166 }
      documentation: US jurisdiction — at least one SNOMED CT coding required.
    - purpose: required
      valueSet: http://example.org/ValueSet/de-icd10gm-conditions
      any: true
      usage:
        - code: { code: DE, system: urn:iso:std:iso:3166 }
      documentation: German jurisdiction — at least one ICD-10-GM coding required.
```

Three features working together here: `required` makes the binding enforceable, `any: true` means "at least one Coding must match" (not all of them — so you can still send other codings alongside), and `usage` scopes each binding to its jurisdiction. A US system only sees the SNOMED requirement; a German system only sees ICD-10-GM. The main extensible binding applies to both.

### Legacy Data Migration

A common problem: a country transitions to a new coding standard, but hospitals have decades of data in the old one. Without additional bindings you'd have to choose — either the binding is loose enough for legacy data (and you lose conformance for new data) or it's strict for new data (and old records fail validation).

Additional bindings solve this with `current` + `minimum`:

```yaml
binding:
  strength: extensible
  valueSet: http://hl7.org/fhir/ValueSet/condition-code
  additional:
    - purpose: current
      valueSet: http://example.org/ValueSet/icd-11-conditions
      documentation: >
        All new diagnoses captured after 2026-01-01 must use ICD-11.
    - purpose: minimum
      valueSet: http://example.org/ValueSet/icd-10-icd-11-conditions
      documentation: >
        Systems must accept both ICD-10 and ICD-11 codes.
```

The main binding stays broad (`condition-code` — extensible). The `current` binding says: "from now on, use ICD-11." The `minimum` binding says: "but every conformant system must still accept ICD-10 in incoming data." Neither replaces the main binding — they add precision where the old model had only one knob to turn.

### Category-Dependent Bindings (SDOH)

This is a real pattern from [US Core SDOH profiles](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Context%20Dependent%20Additional%20Bindings). `Condition.code` is bound to a broad ValueSet, but different categories get tighter bindings — if the category is `food-insecurity`, the code must come from the SDOH Food Insecurity Diagnoses VS; if `housing-instability`, from a different VS. Each additional binding uses `usage` to scope by category value:

```yaml
# Condition.code
binding:
  strength: required
  valueSet: http://hl7.org/fhir/us/core/ValueSet/us-core-condition-code
  additional:
    - purpose: required
      valueSet: http://hl7.org/fhir/us/sdoh/ValueSet/food-insecurity-diagnoses
      usage:
        - code: { code: food-insecurity, system: http://hl7.org/fhir/us/sdoh/CodeSystem/sdoh-category }
      documentation: >
        When Condition.category = food-insecurity,
        code must come from this ValueSet.
    - purpose: required
      valueSet: http://hl7.org/fhir/us/sdoh/ValueSet/housing-instability-diagnoses
      usage:
        - code: { code: housing-instability, system: http://hl7.org/fhir/us/sdoh/CodeSystem/sdoh-category }
      documentation: >
        When Condition.category = housing-instability,
        code must come from this ValueSet.
```

The `usage` context here is not jurisdiction — it's a clinical category within the same resource. The validator checks: if the Condition has `category = food-insecurity`, then the additional binding for food insecurity applies and `code` must be from that specific ValueSet. If the category doesn't match, the binding is ignored. As [Grahame confirmed](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Context%20Dependent%20Additional%20Bindings): "if your category is one of the designated categories, then the value set for that category applies."

### UI Hints for Large ValueSets

Not all additional bindings are about conformance. The `ui` and `starter` purposes carry no validation weight — they help implementers build better interfaces. A vital signs profile might have 100,000+ LOINC codes in its extensible binding, but the UI only needs a handful:

```yaml
# Observation.code (vital signs)
binding:
  strength: extensible
  valueSet: http://hl7.org/fhir/ValueSet/observation-vitalsignresult
  additional:
    - purpose: starter
      valueSet: http://example.org/ValueSet/common-vitals
      documentation: >
        15 most common vital sign codes. Start here
        if you're building a new system.
    - purpose: ui
      valueSet: http://example.org/ValueSet/vitals-picker
      documentation: >
        Curated list optimized for dropdown selection.
        Not a conformance constraint.
```

The `starter` purpose says "begin your implementation with these codes." The `ui` purpose says "show these codes in a picker." Neither affects validation — a system can send any code valid under the main extensible binding. But they solve a real problem: implementers staring at a 100K-code ValueSet with no idea where to begin.

## Still Evolving

The mechanism is mature, but the community is still working out edge cases. As recently as late 2025, Eric Haas [wrote](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Implementiing%20Additional%20Bindings%20for%20each%20purpose%20unclear): "We discussed this topic at the WGM, and as a profiler, I am still confused about how additional bindings are supposed to work." He and Gay Dolin are creating a matrix of explicit use cases with examples for the [FHIR Guidance IG](https://build.fhir.org/ig/FHIR/ig-guidance/). Key areas being refined:

- **Conditional bindings** — using `usage` (UsageContext) to scope bindings by jurisdiction or workflow is powerful but [complex in practice](https://chat.fhir.org/#narrow/stream/implementers/topic/Conditional%20additional%20bindings) (66 messages and counting).
- **Validator behavior** — how violations are reported varies by purpose: `required` produces errors, guidance purposes like `starter` and `ui` are informational only.

## References and Community Discussions

**Spec:**

- [ElementDefinition.binding.additional](http://hl7.org/fhir/R5/elementdefinition-definitions.html#ElementDefinition.binding.additional) — formal definition
- [Additional Binding Purpose Codes](http://hl7.org/fhir/R5/codesystem-additional-binding-purpose.html) — all purpose codes with definitions
- [Terminologies: Binding Strengths](https://www.hl7.org/fhir/terminologies.html#strength) — how strengths work with additional bindings
- [Binding Examples](http://hl7.org/fhir/terminologies-binding-examples.html) — worked examples
- [Tooling Extensions IG](https://hl7.org/fhir/tools/StructureDefinition-additional-binding.html) — R4 backport extension

**Community ([chat.fhir.org](https://chat.fhir.org)):**

- [Extension for additional bindings](https://chat.fhir.org/#narrow/stream/terminology/topic/Extension%20for%20additional%20bindings) — the original 2022 proposal by Grahame Grieve (49 messages)
- [Implementing Additional Bindings for each purpose](https://chat.fhir.org/#narrow/stream/IG.20creation/topic/Implementiing%20Additional%20Bindings%20for%20each%20purpose%20unclear) — AND vs OR semantics, use case matrix (27 messages)
- [Conditional additional bindings](https://chat.fhir.org/#narrow/stream/implementers/topic/Conditional%20additional%20bindings) — UsageContext scoping (66 messages)
- [Extensible+Max](https://chat.fhir.org/#narrow/stream/argonaut/topic/Extensible%2BMax) — the pre-R5 pattern (182 messages)
- [Modifications to ElementDefinition](https://chat.fhir.org/#narrow/stream/fhir.2Finfrastructure-wg/topic/Modifications%20to%20ElementDefinition) — R5 formal commitment (111 messages)

See also: [Residual Categories in FHIR Terminology](/blog/residual-categories-in-fhir-terminology).
