NHS UK Core FHIR Profiles: A Developer’s Guide to Building Conformant Healthcare APIs

Written by Technical Team Last updated 11.09.2026 18 minute read

Home>Insights>NHS UK Core FHIR Profiles: A Developer’s Guide to Building Conformant Healthcare APIs

Building an API that exchanges FHIR resources with NHS systems requires more than producing syntactically valid FHIR R4 JSON. A resource can be valid against the base HL7 FHIR specification while still failing the constraints, terminology rules, identifier conventions or implementation requirements expected by a UK healthcare interface.

The practical approach is to treat FHIR as a layered contract. At the lowest layer sits the HL7 FHIR R4 specification. UK Core then constrains and extends those base resources for UK health and care. Individual NHS APIs, programmes and local implementations may add another layer of constraints on top. Your application therefore needs to implement the exact combination of resource definition, profile, terminology and API behaviour required by the interface it is consuming or exposing.

This guide explains how to engineer that conformance into an API from the beginning, rather than attempting to correct validation errors once integration testing starts.

1. Start with the exact UK Core artefacts your API must implement

Begin by identifying the implementation guide and package version that forms the contract for your integration.

Do not start by searching for a generic example of a FHIR Patient, Observation or MedicationRequest resource and adapting it. Base FHIR examples are useful for understanding the resource model, but they do not define the complete UK implementation.

A typical implementation hierarchy looks approximately like this:

  • HL7 FHIR R4 defines the underlying resource, data types, REST interaction model and base terminology.
  • UK Core constrains those resources and supplies UK-specific profiles, extensions, identifier conventions, terminology artefacts and implementation guidance.
  • An NHS API or programme-specific implementation guide may constrain UK Core further for a particular workflow.

The distinction matters during development because each successive layer can reduce optionality.

Key point: Building a conformant NHS FHIR API means validating against the correct NHS UK Core FHIR profile, not just the base HL7 FHIR R4 specification. Developers should pin the exact UK Core package version required by the target NHS API and use that version consistently across development, automated testing and production validation.

For example, the base FHIR Patient resource allows multiple identifiers and places relatively broad constraints on their contents. A UK Core Patient profile introduces a recognisable NHS number identifier slice, including the NHS number namespace. An NHS API built on that profile may then specify when the NHS number is required, which search parameters must be supported and how patient identity must be resolved.

Treat the implementation guide used by the target API as the top-level specification.

Pin its dependencies in your project rather than resolving them dynamically during builds. A later release of UK Core may contain changed terminology, altered cardinalities, corrected profile definitions or new extensions. Automatically switching versions can consequently make an unchanged application validate differently between builds.

The same rule should apply to your validation environment. Development, CI, test and production tooling should validate against the same package versions. A developer validating against one UK Core package while the integration environment validates against another creates failures that can be extremely difficult to diagnose.

For every profile you implement, capture the following information as an explicit engineering artefact:

the canonical profile URL; the profile version; the underlying FHIR resource; mandatory elements; Must Support elements; sliced elements; fixed values; terminology bindings; relevant invariants; allowed references; required extensions; modifier elements; and any additional constraints introduced by the API-specific implementation guide.

This turns an extensive implementation guide into a finite set of rules your application can implement and test.

Pay particular attention to canonical URLs. FHIR identifies profiles, extensions, ValueSets and CodeSystems through canonical identifiers. These are machine identifiers, not merely documentation links. Copy them from the relevant definition rather than reconstructing them from memory or assuming that a similarly named artefact has the same canonical URL.

When your resource declares conformance through meta.profile, use the canonical profile URI expected by the implementation. Do not place the URL of the human-readable documentation page into meta.profile.

Also avoid treating meta.profile as something that makes a resource compliant. It is an assertion about the resource, not a transformation. Adding a UK Core canonical URL to an otherwise non-conformant FHIR resource does not convert it into a UK Core resource.

A useful development pattern is to maintain a small internal conformance catalogue alongside the codebase. Each implemented API resource can be mapped to its canonical profile and version. Developers can then trace application models and tests directly back to the specification they implement.

2. Turn UK Core profile definitions into application rules

Once you have pinned the correct specification, translate each relevant StructureDefinition into code-level behaviour.

A FHIR profile is not simply a schema showing which properties exist. It can constrain the same element in several different ways simultaneously. Your implementation therefore needs to read the profile through its differential or generated snapshot rather than relying on the top-level resource diagram alone.

Start with cardinality.

An element constrained to 1..1 needs to be populated exactly once. An element constrained to 0..1 must never be emitted repeatedly. An element with an upper cardinality of * can repeat, but slices within that repeating element may each have their own cardinalities.

This becomes particularly important for elements such as identifier, extension, telecom and coding.

Consider patient identifiers. Your internal domain model may contain NHS numbers, hospital numbers, EPR identifiers and identifiers from external systems. Mapping all of these to repeated Patient.identifier objects is structurally straightforward, but UK Core can define a named slice for the NHS number.

Your serializer therefore needs to recognise that an NHS number is not merely another arbitrary identifier. It must populate the identifier in the form constrained by the NHS number slice, including the correct identifier system.

The NHS number namespace used by UK Core is https://fhir.nhs.uk/Id/nhs-number. Treat that URI as data. Do not replace it with an organisation-specific namespace, alter its capitalisation or use the API endpoint URL as the identifier system.

The resource.id must also remain separate from the patient’s NHS number. Patient.id identifies a FHIR resource on a particular server. Patient.identifier carries business identifiers for the actual patient. Using an NHS number as a database key internally may be an implementation decision, but it must not cause your FHIR layer to confuse logical resource identity with business identity.

Next, inspect slicing.

FHIR slicing is one of the most common sources of apparently mysterious validation errors. A repeating element can be divided into named slices according to a discriminator such as a URL, value, profile or type. The validator then determines which slice an instance belongs to.

Your application should construct recognised slices intentionally instead of creating generic elements and hoping that a validator classifies them as expected.

Extensions work in a similar way. UK Core uses extensions where required information is not represented directly by an appropriate base FHIR element. An extension must carry its exact canonical URL and the data type defined by that extension.

Do not invent a local extension whenever an existing UK Core extension represents the concept. Equally, do not place data into a vaguely related standard field solely to avoid using an extension. The objective is to represent the semantic meaning prescribed by the profile.

Modifier elements deserve separate treatment in your application model. FHIR marks certain elements as modifiers because their values can change the interpretation of the resource. Status fields, active, deceased[x] and other modifier concepts should not be silently ignored when consuming resources. A generic deserialiser that extracts only the fields your screen currently displays can therefore produce unsafe behaviour if it discards information that changes how the resource should be interpreted.

Must Support also needs explicit implementation decisions. Do not automatically interpret Must Support as equivalent to cardinality 1..1. Cardinality determines whether the element must physically exist in a conformant resource. Must Support indicates that implementations covered by the relevant specification must support the element according to the implementation guidance.

For each Must Support element, define what support means for your role. A producer may need to populate it when data is available. A consumer may need to receive, persist or correctly process it. A server may need to support searching against it if the API specification says so.

Document that behaviour in tests rather than leaving Must Support as an annotation developers see only in profile documentation.

UK Core FHIR Conformance Artefacts Developers Need to Handle

A UK Core implementation is governed by more than the StructureDefinition behind an individual Patient, Observation or MedicationRequest profile. FHIR uses several machine-readable conformance resources to define how information is structured, which coded values are permitted and how an API is expected to behave.

When implementing an NHS FHIR API, developers should identify which of these artefacts are included by the relevant implementation guide and incorporate them into validation, testing and API configuration rather than treating them purely as documentation.

FHIR artefact What it controls How to handle it in an NHS API
StructureDefinition Defines resource profiles and extensions, including cardinalities, slicing, permitted data types, bindings, invariants and constrained references. Load the exact UK Core StructureDefinition version into validation tooling and test every resource produced by the API against the required profile.
ValueSet and CodeSystem Define the coded terminology that can be used in clinical and administrative elements. Validate the system-and-code combination rather than display text alone, and apply the binding strength specified by the relevant profile.
ConceptMap Describes mappings between different coding systems or information models. Use explicit mappings when translating data between local terminology and the standard terminology required by the NHS FHIR interface rather than embedding ad hoc conversions in serializers.
NamingSystem Defines namespaces used to identify organisations, patients, systems, terminologies and other entities consistently. Maintain identifier namespaces as controlled configuration so that values such as an NHS number are always emitted with the correct system URI.
SearchParameter Defines how resources can be queried, including the parameter type and the FHIR element or expression against which a search operates. Implement searches according to the formal SearchParameter definition and test token, reference, date and string behaviour rather than treating every query as a generic database filter.
OperationDefinition Defines FHIR operations beyond the standard read, create, update and search interactions, including their input and output parameters. Implement the operation contract exactly, including supported resource scope, parameter names, data types and response structure.
CapabilityStatement Describes the profiles, resource types, interactions, operations, formats and search capabilities supported by a FHIR server. Generate or maintain it from the API’s real capabilities and test it against deployed behaviour so clients are not told that unsupported functionality is available.

3. Model identifiers, references, terminology and extensions correctly

After implementing structural constraints, move to semantic conformance. Most difficult interoperability defects occur here because a resource can look perfectly reasonable as JSON while carrying information in a form another healthcare system cannot reliably interpret.

Start with identifiers by defining a namespace registry inside your integration layer.

Every business identifier should be represented as a combination of its identifier system and value. Do not compare identifiers by value alone. The value 123456 in one hospital-number namespace is not necessarily the same identifier as 123456 in another.

Normalise only where the governing identifier specification permits it. Avoid a generic “clean identifier” function that strips punctuation, converts case or removes leading zeroes from every identifier entering the system.

For patient identity, retain the distinction between the NHS number itself and any metadata attached to that number. UK Core can represent information such as NHS number verification status through an extension on the identifier. If the source system provides that information and the receiving specification expects it, map it into the appropriate extension instead of concatenating it into a string or inventing a second identifier.

References require the same level of precision.

Before generating a reference such as an Observation.subject, MedicationRequest.subject or Encounter.participant, determine which target profiles are permitted by the profile you are implementing. Do not assume that any resource of the correct base type is automatically acceptable.

Your FHIR layer should also distinguish between references to resources contained in the same Bundle, relative references on the same FHIR server and absolute references to external locations. Pick a reference strategy for the API contract and apply it consistently.

When constructing transactional or document-style Bundles, generate stable fullUrl values and ensure references resolve unambiguously within the Bundle. Test reference integrity independently from profile validation; a collection of individually valid resources can still form a broken Bundle if internal references point nowhere.

Terminology should be treated as executable configuration rather than display text.

A CodeableConcept normally contains one or more Coding structures. A Coding carries a system, code and optionally a display. The machine meaning comes principally from the system-and-code combination. The display is not a safe substitute for the code.

When the relevant profile defines a required ValueSet binding, only codes permitted by that binding should be emitted. When the binding strength is extensible, preferred or example, implement the corresponding FHIR semantics rather than reducing every binding to the same Boolean “valid or invalid” test.

UK healthcare integrations commonly involve terminologies such as SNOMED CT and dm+d. Store the actual terminology identifiers and codes supplied by your authoritative source. Do not derive clinical codes from free text during serialisation.

A particularly useful architecture is to separate terminology translation from FHIR serialisation. Your domain service should first determine the correct coded clinical concept. The FHIR mapper should then represent that concept in the profile-compliant element.

This prevents serializers from becoming collections of hidden clinical mapping rules.

Apply the same separation to extensions. Create typed application components for extensions that your platform uses frequently rather than constructing arbitrary url and value[x] pairs throughout the codebase. A typed NHS number verification extension, for example, is substantially easier to validate and refactor than dozens of manually assembled extension objects.

At the boundary of the system, validate four semantic properties independently:

  • identifiers use the expected namespaces, values and slices;
  • coded elements use the expected CodeSystems and satisfy applicable ValueSet bindings;
  • references point to resources permitted by the relevant profile and can be resolved as expected;
  • extensions use the exact canonical URL, permitted location and correct value[x] type.

That separation makes failures far easier to diagnose than returning a single generic “FHIR validation failed” error.

4. Build profile validation into development, testing and deployment

Treat a FHIR validator as part of your build toolchain rather than as an integration-test utility.

A good validation pipeline operates at several layers.

The first layer validates serialization. Every resource your application emits should be legal FHIR R4 JSON or XML. Primitive types, arrays, choice elements such as value[x], date formats and resource structures need to be correct before profile rules are evaluated.

The second layer validates against the required UK Core profile. Load the exact package dependency selected for your implementation and tell the validator which profile the resource is expected to satisfy.

The third layer applies the API-specific implementation guide where one exists. This catches constraints introduced above UK Core.

The fourth layer checks terminology using the terminology resources required by the implementation. Make sure terminology validation is deterministic in CI. A build that depends on whatever terminology content happens to be returned by a remote service at that moment can produce inconsistent results.

Create positive and negative fixtures for every resource type.

Positive fixtures should represent realistic NHS data rather than the smallest JSON object capable of passing validation. Include multiple names, identifiers, telecom entries, addresses, extensions, coded concepts and references where the workflow can genuinely contain them.

Negative fixtures should each violate one deliberate rule.

Test a missing mandatory element. Test the wrong identifier namespace. Test an invalid code. Test the wrong extension value type. Test too many elements where cardinality permits one. Test a reference to the wrong target profile. Test an unrecognised slice where closed slicing is used. Test an invalid date or an impossible choice-element combination.

Negative tests matter because they demonstrate that your validator is actually loading the intended profile. A test suite in which every fixture is valid can continue passing even if profile loading silently breaks and validation falls back to base FHIR.

Do not rely entirely on a FHIR library’s generated classes either.

Most FHIR SDKs model the base FHIR specification. They can prevent many structural errors at compile time, but they cannot automatically guarantee conformance with every UK Core profile. A Patient object that successfully serialises through a FHIR SDK may still violate UK Core constraints.

Your test pipeline should therefore validate the serialised resource, not simply the in-memory object.

Store validator output as a structured test artefact. FHIR validators commonly report issues through OperationOutcome-like structures containing severity, diagnostic information and location expressions. Parse these results so that CI failures identify the resource, profile and FHIRPath location responsible for the failure.

For bulk interfaces, also test complete Bundles. Validate individual resources first, then the Bundle itself, then referential integrity and finally workflow-level rules.

Add production-boundary validation selectively.

Validating every outbound resource in production can be useful for high-risk interfaces, but it can also add significant latency when terminology and complex profile resolution are involved. A common pattern is to perform exhaustive validation in development and CI, then use controlled runtime validation for ingress data, newly deployed mappings, high-risk operations or sampled traffic.

Never silently repair an invalid clinical resource without preserving evidence of the transformation. If incoming data uses an incorrect code system or malformed identifier, changing it automatically can alter meaning. Route transformation through explicit mapping rules, capture the original value where appropriate and expose failures through operational monitoring.

Version your test fixtures alongside profile dependencies. When the implementation guide changes, run the previous fixture set against the new dependency before changing application code. The resulting validation differences give the engineering team a concrete migration list.

5. Design UK Core conformance into the API lifecycle

Profile conformance should continue beyond the shape of individual resources and into API operations.

Start with your CapabilityStatement.

If you are exposing a FHIR server, make the CapabilityStatement accurately describe the behaviour implemented by the server: resource types, supported profiles, interactions, search parameters, formats and relevant operations.

Do not advertise a profile simply because your database contains approximately equivalent information. The server should be able to send and receive resources according to the contract it declares.

For searches, implement the parameters defined by the API contract using FHIR’s actual search semantics. Identifier searches are particularly important. Searching for an NHS number should operate using the identifier namespace and identifier value rather than performing a generic text search across patient data.

If your server accepts writes, decide where conformance is enforced.

A robust request flow parses the FHIR payload, checks the declared and expected resource type, validates against the required profile, validates terminology and references as appropriate, applies authorisation and business rules, persists the resulting representation and returns a standards-compliant response.

Keep profile validation errors separate from business-rule failures. A structurally invalid MedicationRequest is a different problem from a structurally valid MedicationRequest that the application is not authorised to create.

Return useful OperationOutcome information where the API contract permits it. Include enough detail for an integrating engineer to locate the failing FHIR element without exposing internal infrastructure or sensitive data.

Design updates around resource identity rather than business identity. A PUT to a FHIR endpoint addresses a logical resource, whereas an NHS number identifies the patient represented by that resource. Do not allow those identities to become interchangeable inside routing or persistence code.

For concurrent updates, implement the versioning behaviour required by your API. When optimistic concurrency is supported, preserve the distinction between the logical resource ID and its version. This becomes particularly important when multiple clinical systems can update the same representation.

Bundle handling should be transactional where the workflow requires atomicity. If a Bundle contains several related changes that must either all succeed or all fail, validate the complete request and its dependencies before committing partial updates.

Finally, make profile upgrades an explicit engineering process rather than a dependency-management task.

When a newer UK Core or API-specific implementation guide is introduced, compare the old and new packages at the StructureDefinition, terminology and extension level. Identify changed cardinalities, newly introduced constraints, retired artefacts, altered canonical dependencies, changed ValueSet bindings and corrected reference targets.

Run existing production-shaped fixtures against both versions.

Where both versions must be supported during migration, keep the mappings distinct. Do not attempt to create one vague “UK Core compatible” serializer containing conditionals for every profile release. Build a stable internal clinical model and explicit adapters for each external contract.

This makes it possible to update the interoperability layer without allowing external profile changes to propagate throughout the application.

The final implementation should make conformance visible in the repository. A developer looking at a Patient, Observation or MedicationRequest integration should be able to determine which profile it implements, which package supplies that profile, how each constrained element is mapped and which automated tests prove the resulting resource is conformant.

That is the practical standard to aim for when building NHS-facing FHIR APIs: not JSON that resembles UK Core, but an implementation in which profile definitions, identifiers, terminology, extensions, references, validation and API behaviour are all treated as versioned, testable engineering contracts.

Need help with healthcare API development?

Is your team looking for help with healthcare API development? Click the button below.

Get in touch