SMART functional conformance passes. Inferno is green functionally; HTTPS/TLS closure is the remaining SMART gate. View the roadmap

What Does meta.profile Actually Mean to a FHIR Server?

Ilavrita Engineering · September 26, 2026

What Does meta.profile Actually Mean to a FHIR Server?

Version: dev
Source revision: 1aa13a38a06ae151d5a73713132b487d2a7c5216
Last verified: September 26, 2026
FHIR version: R4 4.0.1
Examples: synthetic data only

If a FHIR resource declares a URL in meta.profile, it is making a claim: “this resource conforms to this StructureDefinition.”

A server has three honest choices when it sees that claim:

  1. it can resolve the profile and check the resource against it,
  2. it can reject the claim because it cannot evaluate it,
  3. or it can explicitly report that the profile was not checked.

What it should not do is silently accept a conformance claim it never evaluated.

That is the boundary Ilavrita is implementing now.

At the current dev revision, Ilavrita can load implementation-guide profiles that ship snapshots, resolve declared meta.profile URLs, apply narrowed cardinality, enforce resource-level profile constraints it can evaluate, refuse a profile that constrains a different resource type, and report an unknown profile instead of pretending it passed.

That still does not mean “full profile validation.” Slicing, narrowed profile types and bindings, extension context, automatic profile inference, differential expansion, and element-context invariants remain incomplete or deliberately unsupported.

This post is about that narrower implementation boundary.

Ilavrita remains a developer preview and should not hold patient data.

What meta.profile means in FHIR R4

FHIR's Meta.profile element carries canonical URLs for profiles the resource claims to conform to.

The important word is claims.

A profile is a StructureDefinition that constrains a base FHIR resource or datatype. A Patient profile might require an identifier that base R4 leaves optional, narrow a repeating field, bind a code to a smaller value set, introduce slicing, or attach additional invariants.

Primary references:

A resource declaring:

json
{
  "resourceType": "Patient",
  "meta": {
    "profile": [
      "https://example.org/fhir/StructureDefinition/MyPatient"
    ]
  }
}

is therefore saying something stronger than “I am structurally a Patient.”

It is also saying “I satisfy the additional constraints in MyPatient.”

A validator that reads only the base Patient definition cannot prove that second statement.

Ilavrita only applies profiles the install actually holds

Implementation guides are not vendored into the Ilavrita repository by default.

Instead, an operator or test environment provides a guide directory:

bash
./scripts/ig/fetch.sh
export ILAVRITA_PROFILE_DIR=.ig/ndhm/package

The default fetch path is currently used for India's ABDM implementation guide during conformance work.

The loader reads profiles from the supplied package and indexes them by canonical URL.

Source:

Versioned canonical references are normalized. A resource naming:

text
https://example.org/fhir/StructureDefinition/MyPatient|6.5.0

can resolve to the same loaded profile as the unversioned canonical URL.

There is a specific test for that behavior.

Unknown profiles are reported, not silently passed

Suppose a resource says:

json
{
  "resourceType": "Patient",
  "meta": {
    "profile": [
      "https://example.org/fhir/StructureDefinition/NobodyLoadedThis"
    ]
  }
}

If the install has never loaded that profile, Ilavrita does not have enough information to say whether the resource conforms.

The current implementation records a validation warning along the lines of:

This install does not hold that profile, so the resource was not checked against it.

That is intentionally different from rejecting the resource.

FHIR resources can legitimately refer to profiles defined elsewhere. Not having the package locally is not automatically a client error.

But accepting the declaration without comment would be misleading.

The behavior is covered by TestAnUnheldProfileIsReportedNotPassed.

Source:

A profile cannot change the resource type it constrains

A profile has a base type.

A Patient profile cannot honestly be declared by an Organization just because both are otherwise valid FHIR resources.

Ilavrita checks that relationship.

The server test suite submits a valid Organization that declares the base Patient StructureDefinition:

json
{
  "resourceType": "Organization",
  "name": "Example",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/StructureDefinition/Patient"
    ]
  }
}

The resource is refused because the profile constrains Patient, not Organization.

Declaring the Organization definition on an Organization is accepted.

This is a useful minimum proof that the server is reading the declared profile rather than merely checking whether a URL exists.

See TestAProfileIsCheckedAgainstTheTypeItConstrains in:

apps/ilavrita/validate_test.go

Narrowed cardinality is applied

Cardinality is one of the first useful things an implementation guide changes.

Base R4 may allow:

text
0..*

while a profile narrows that to:

text
1..1

Those constraints mean different things:

  • min = 1: the element must be present,
  • max = 1: no more than one occurrence is allowed.

Ilavrita now reads those narrowed bounds from the profile snapshot.

The tests search the loaded guide for a Patient profile that makes an element required, build a Patient without that element, and assert that validation produces an issue.

That is TestAResourceMissingWhatItsProfileRequiresIsRefused.

The implementation also distinguishes cardinality from JSON shape.

A profile narrowing 0..* to 0..1 does not turn a FHIR array into a JSON object. The base definition still decides whether the JSON representation is an array. The profile only narrows how many array entries may appear.

That bug existed during implementation and was caught by comparing Ilavrita's result against a second validator.

This is the sort of profile behavior that looks small in code and causes interoperability bugs if implemented incorrectly.

Snapshot profiles only

Ilavrita currently loads a profile only when the implementation guide provides a snapshot.

A differential says how a profile differs from its base definition. To validate it directly, a server first has to expand that differential through its inheritance chain into a complete effective definition.

Ilavrita does not currently perform that expansion.

So a differential-only profile is outside the supported boundary.

This is explicitly documented in docs/profiles.md and implemented by the profile loader.

That matters because “we can load StructureDefinitions” is much broader than “we can correctly evaluate this class of StructureDefinitions.”

The current claim is the latter, constrained to snapshots.

What is still not applied

This is the section that keeps “profile validation” from becoming an overclaim.

At the current revision, Ilavrita does not fully apply every kind of profile constraint.

Slicing

FHIR slicing lets a profile describe multiple constrained subsets of a repeating element.

For example, a Bundle profile may define several different expected forms of Bundle.entry.

Ilavrita currently skips slicing rather than incorrectly merging slice definitions into the base element.

That is deliberate.

A slice often shares the same element path as the element it narrows. Treating one slice as if it were the element itself could replace the real cardinality with one branch's cardinality and reject valid resources.

Narrowed types and profile-specific bindings

The current docs still list narrowed types and narrowed profile bindings as incomplete.

Base R4 required bindings are a separate capability and are checked from the embedded R4 definitions.

That should not be confused with evaluating every terminology restriction introduced by an implementation guide.

Element-context invariants

Ilavrita evaluates R4's required base invariants, but profile constraints attached below the resource root require the validator to establish the correct element context before evaluating them.

That path-resolution work is not complete.

Extension context

An extension definition can constrain where the extension may legally appear.

The current cross-validator work identifies extension context as a known gap.

Automatic profile inference

Ilavrita validates profiles a resource explicitly names in meta.profile.

Some validators infer additional profiles from resource content — for example, applying R4 vital-signs profiles to Observations based on their codes.

Ilavrita does not currently do that.

That difference is measured rather than hidden.

ABDM 6.5.0 is being used as a real implementation-guide test

The current profile work is not based only on hand-written fixtures.

The repository's implementation-guide tooling fetches the ABDM guide and validates the examples published with it.

The documented current result is:

138 examples checked, 0 refused by Ilavrita.

That statement needs context.

It does not mean Ilavrita implements every ABDM rule.

It means the guide's published examples pass the subset of profile behavior Ilavrita currently implements.

The repository also cross-checks those examples against the HL7 validator service.

The cross-check is designed to fail on unexplained disagreement. Known classes of difference are explicitly counted and documented rather than swallowed.

Current documented categories include:

  • terminology checks requiring external systems while CI is offline,
  • downstream slicing findings caused by those terminology gaps,
  • R4 vital-sign profile inference that Ilavrita does not perform,
  • extension-context validation that Ilavrita does not yet implement.

See:

scripts/ig/crosscheck.py

and:

docs/profiles.md

The useful property is not the number 138 by itself.

It is that disagreements have names.

The cross-check also protects against a vacuous pass

There is a subtle failure mode in validator comparison:

a validator that never loaded the implementation guide may accept every example.

That can look like agreement.

The repository records that an earlier version of the cross-check made exactly this mistake.

The current script first proves the guide is loaded by validating a bare Patient against the ABDM Patient profile and requiring the result to name an element the guide makes mandatory.

If that control does not fail in the expected way, the cross-check stops.

It also refuses to treat Ilavrita's side as agreement if the Ilavrita test did not actually run.

This is a good example of why conformance tooling itself needs negative controls.

A test that never exercised the thing under test is not evidence.

$validate reports rather than stores

Ilavrita exposes:

http
POST /fhir/R4/{type}/$validate

The operation evaluates the resource and returns an OperationOutcome.

A validation operation that found problems still returns HTTP 200 when the operation itself ran successfully; the issues are in the OperationOutcome.

The server tests also confirm that calling $validate does not store the resource.

That distinction matters for client workflows that want to inspect a synthetic resource before attempting a write.

The CapabilityStatement advertises $validate only because the route is actually served, and the test suite checks that every advertised type exposes the operation consistently.

See:

apps/ilavrita/validate_test.go

How to reproduce the profile checks

The intended clean-clone flow is:

bash
git clone https://github.com/Ilavrita/Ilavrita.git
cd Ilavrita
git checkout 1aa13a38a06ae151d5a73713132b487d2a7c5216

make bootstrap
./scripts/ig/fetch.sh

export ILAVRITA_PROFILE_DIR=.ig/ndhm/package

Then run the profile tests:

bash
go test ./packages/validate/   -run 'GuidesOwnExamples|TestAResourceMissingWhatItsProfileRequiresIsRefused|TestAnUnheldProfileIsReportedNotPassed'   -count=1

And the broader server behavior:

bash
go test ./apps/ilavrita   -run 'TestAProfileThisServerDoesNotHoldIsSaidSoRatherThanPassed|TestAProfileIsCheckedAgainstTheTypeItConstrains'   -count=1

For the second-validator comparison:

bash
python3 scripts/ig/crosscheck.py

Verification note: I attempted the clean clone for this article, but the publishing runtime could not resolve github.com, so the shell verification could not execute here. The current source, docs, and tests were instead inspected directly through the connected GitHub repository API at the pinned revision. This article therefore does not claim a fresh local test execution.

Documentation drift found during this verification

There is still stale introductory documentation at the current revision.

The top of README.md says nothing checks a resource against a profile.

The opening section of docs/known-limitations.md says the same.

But later sections of those documents, ROADMAP.md, docs/profiles.md, HANDOFF.md, the implementation, and the tests all describe partial declared-profile validation as implemented.

The older introductory language is therefore not used as the capability source for this article.

This is a documentation bug and should be corrected.

For a project built around evidence-first claims, contradictory documentation is itself a conformance problem: a careful reader cannot know which statement is authoritative without reading the source and tests.

What this proves — and what it does not

At revision 1aa13a38a06ae151d5a73713132b487d2a7c5216, the evidence supports these claims:

  • implementation-guide profiles with snapshots can be loaded,
  • declared meta.profile URLs are resolved,
  • versioned canonical profile URLs resolve to the underlying profile,
  • an unknown profile is reported instead of silently treated as checked,
  • a profile constraining the wrong resource type is refused,
  • narrowed cardinality is applied,
  • ABDM guide examples are exercised in the test workflow,
  • unexplained disagreement with the second validator is treated as a failure.

It does not support these broader claims:

  • complete FHIR profile validation,
  • complete ABDM conformance,
  • complete terminology validation,
  • slicing support,
  • extension-context validation,
  • full differential expansion,
  • automatic profile inference,
  • production readiness,
  • HIPAA, GDPR, EHDS, or other compliance.

Those limits are not footnotes.

They define what the current feature actually is.

FAQ

Does Ilavrita validate meta.profile?

Partially. It validates declared profiles the install can resolve and applies the subset of profile constraints the current validator implements. Unknown profiles are reported rather than silently passed.

Does Ilavrita support implementation guides?

It can load StructureDefinition profiles from a supplied implementation-guide package when those profiles contain snapshots. Differential-only expansion is not implemented.

Does it support ABDM profiles?

ABDM 6.5.0 is currently used in the implementation-guide validation workflow, and 138 published examples are exercised. That is not equivalent to claiming complete ABDM conformance.

Does profile validation include slicing?

No. Slicing is explicitly outside the current applied subset.

Does a profile URL the server cannot resolve make the resource invalid?

Not automatically. The current behavior reports that the profile was not checked instead of falsely claiming conformance or treating an external canonical URL as inherently invalid.


Evidence

For the rest of the current boundary, see docs, known limitations, and the roadmap.

Back to blog