fhir-resource-diff
v0.3.4
Published
CLI and library for diffing and validating FHIR JSON resources
Maintainers
Readme
fhir-resource-diff
A dedicated CLI and TypeScript library for diffing, validating, and normalizing FHIR R4 / R4B / R5 JSON — with structured output designed for CI pipelines and AI agents.
What is FHIR?
FHIR (Fast Healthcare Interoperability Resources) is the modern standard for exchanging healthcare data, published by HL7. This tool works with FHIR JSON resources — the building blocks of healthcare APIs. → Learn more at hl7.org/fhir
Why this exists
FHIR resources evolve across API versions, profiles, and integration points. Comparing them, validating them, and understanding what changed between versions are daily developer needs — but dedicated diff and validation tooling for the JavaScript and TypeScript ecosystem has been sparse. fhir-resource-diff addresses this: a fast, local tool built for CI pipelines, developer workflows, and AI agents that need programmatic access to FHIR payloads without a server-side dependency.
Features
- Multi-version support: R4, R4B, R5 — auto-detected or explicit via
--fhir-version - Structural diff with path-level change tracking
- Structural validation with severity levels (error, warning, info)
- Resource type lookup with HL7 documentation links (
infocommand) - Resource discovery via
list-resources - Stdin/pipe support — compose with
curl,jq, and other unix tools - Machine-consumable JSON output with summary counts, doc URLs, and metadata envelope
--quietmode for headless CI (exit code only, no stdout)--exit-on-difffor CI gate checks- Named presets for common ignore patterns (
metadata,clinical,strict) - Full TypeScript library API — import
diff(),validate(),parseJson()directly
The FHIR TypeScript ecosystem
The JavaScript/TypeScript FHIR community has built excellent tools across the stack
— type systems, API clients, platform SDKs, auth libraries, and IG authoring tools.
Each project solves a different slice of the problem, and fhir-resource-diff is
designed to complement them, not compete.
Where each tool shines
| Focus area | Tool | What it does best |
|---|---|---|
| Type definitions | @types/fhir, @medplum/fhirtypes | TypeScript interfaces for FHIR resources — essential for type-safe application code |
| Platform SDK | @medplum/core | Full-featured FHIR client with profile validation, FHIRPath, and the Medplum platform |
| XML/JSON serialization | fhir (Lantana) | FHIR XML ↔ JSON conversion and JSON Schema validation — one of the earliest FHIR JS tools |
| Auth & API client | fhirclient | SMART on FHIR auth flows and API calls, maintained by SMART Health IT at Boston Children's |
| Conformance validation | HL7 FHIR Validator | The reference implementation for full profile, terminology, and invariant validation |
| Diff, fast validation, CI/CD | fhir-resource-diff | Structural diffing, format validation, and automation-first output |
What this tool adds to the ecosystem
fhir-resource-diff focuses on three areas where we saw a gap in the existing
tooling — not because other tools should have built these, but because they fall
outside the scope of what type libraries, API clients, and platform SDKs are designed
to solve:
FHIR-aware structural diff. Compare two resources path by path and get a classified list of additions, removals, and changes — with dot-notation paths, array index tracking, and ignore presets for metadata noise.
AI agent and automation friendly. Every command supports --format json for
structured output, --envelope for metadata wrapping (tool version, FHIR version,
timestamps, HL7 doc URLs), and stdin pipes for in-memory payloads. An agent can
validate and diff FHIR payloads without writing temp files, parse the output in one
pass, and follow the documentation links — no second tool call needed.
CI/CD native. --exit-on-diff fails the step when resources diverge.
--quiet suppresses stdout for exit-code-only gates. Exit codes are
severity-aware — warnings and info findings never produce non-zero exits.
JSON envelope output includes summary counts for automated triage.
Using them together
These tools work well in combination:
@types/fhiror@medplum/fhirtypesfor your application's TypeScript types,fhir-resource-difffor runtime validation and diffingfhirclientfor SMART auth and API transport, then pipe responses intofhir-resource-difffor validation and comparison- HL7 FHIR Validator for full profile conformance checks in staging,
fhir-resource-difffor fast local validation and CI gates in the development loop
Supported FHIR versions
| Version | Status | Spec URL | |---------|--------|----------| | R4 (4.0.1) | Default, fully supported | https://hl7.org/fhir/R4/ | | R4B (4.3.0) | Supported | https://hl7.org/fhir/R4B/ | | R5 (5.0.0) | Supported | https://hl7.org/fhir/R5/ |
Install
npm install -g fhir-resource-diff
# or
pnpm add -g fhir-resource-diffFor project-local installation: pnpm add -D fhir-resource-diff
Quick start
Compare two resources:
fhir-resource-diff compare examples/patient-a.json examples/patient-b.jsonValidate a resource:
fhir-resource-diff validate examples/patient-a.json
# → validValidate with a specific FHIR version:
fhir-resource-diff validate examples/r5/patient-a.json --fhir-version R5Output diff as JSON:
fhir-resource-diff compare a.json b.json --format jsonCLI reference
compare
fhir-resource-diff compare <file-a> <file-b> [options]
Arguments:
file-a, file-b File paths or - to read from stdin
Options:
--format <fmt> text | json | markdown (default: text)
--fhir-version <ver> R4 | R4B | R5 (default: auto-detect or R4)
--ignore <paths> comma-separated paths to ignore (e.g. meta.lastUpdated,id)
--preset <name> metadata | clinical | strict
--normalize <name> canonical | none
--exit-on-diff exit 1 if differences found (for CI)
--quiet suppress stdout output
--envelope wrap JSON output in metadata envelope (requires --format json)validate
fhir-resource-diff validate <file> [options]
Arguments:
file File path or - to read from stdin
Options:
--format <fmt> text | json (default: text)
--fhir-version <ver> R4 | R4B | R5 (default: auto-detect or R4)
--quiet suppress stdout output
--envelope wrap JSON output in metadata envelope (requires --format json)normalize
fhir-resource-diff normalize <file> [options]
Arguments:
file File path or - to read from stdin
Options:
--preset <name> canonical | none (default: canonical)
--fhir-version <ver> R4 | R4B | R5 (default: auto-detect or R4)
--output <path> write to file instead of stdout
--quiet suppress stdout outputinfo
fhir-resource-diff info <resourceType> [options]
Lookup a FHIR resource type and get its HL7 documentation links.
Options:
--fhir-version <ver> Show docs link for a specific version only
--format <fmt> text | json (default: text)Example:
fhir-resource-diff info Patient
# Patient (base)
# FHIR versions: R4, R4B, R5
# Documentation:
# R4: https://hl7.org/fhir/R4/patient.html
# R4B: https://hl7.org/fhir/R4B/patient.html
# R5: https://hl7.org/fhir/R5/patient.htmllist-resources
fhir-resource-diff list-resources [options]
List known FHIR resource types.
Options:
--fhir-version <ver> Filter to types available in a specific version
--category <cat> foundation | base | clinical | financial | specialized | conformance
--format <fmt> text | json (default: text)Example:
fhir-resource-diff list-resources --category clinical
fhir-resource-diff list-resources --fhir-version R5 --format jsonUse in CI
Use fhir-resource-diff as a CI gate to validate and diff FHIR payloads automatically.
GitHub Actions example:
- name: Validate FHIR resource
run: fhir-resource-diff validate payload.json --format json --fhir-version R4
- name: Diff against expected baseline
run: |
fhir-resource-diff compare expected.json actual.json \
--format json --exit-on-diff --preset metadata --quietKey points:
--exit-on-diffexits 1 when differences are found — fails the CI step--quietsuppresses stdout — useful when you only need the exit code--format jsonproduces machine-parseable output for downstream tooling--format json --envelopewraps results in a metadata envelope with summary counts, tool version, FHIR version, and HL7 documentation URL- Exit codes: 0 = success, 1 = differences found / validation errors, 2 = input error
Use with AI agents, LLMs, and test harnesses
fhir-resource-diff is designed for automated tooling. Agents and test harnesses can pipe FHIR payloads directly without writing temp files.
CLI — agent validates a payload from memory (no temp file):
echo "$FHIR_PAYLOAD" | fhir-resource-diff validate - --format json --fhir-version R4CLI — agent diffs actual vs expected and gets structured output:
echo "$ACTUAL_PAYLOAD" | fhir-resource-diff compare - expected.json \
--format json --envelope --preset metadataThe --envelope JSON output is designed for automated consumers:
{
"tool": "fhir-resource-diff",
"version": "0.2.0",
"command": "compare",
"fhirVersion": "R4",
"timestamp": "2026-03-10T21:00:00.000Z",
"result": {
"resourceType": "Patient",
"identical": false,
"summary": { "added": 5, "removed": 0, "changed": 3, "typeChanged": 0, "total": 8 },
"entries": [...],
"documentation": "https://hl7.org/fhir/R4/patient.html"
}
}An agent can parse this once and know: what changed, how many changes, what FHIR version, and where to find the HL7 docs — without a second tool call.
TypeScript library — agent harness imports directly:
import { parseJson, validate, diff } from "fhir-resource-diff";
const parsed = parseJson(responseBody);
if (!parsed.success) {
throw new Error(`Invalid FHIR JSON: ${parsed.error}`);
}
const validation = validate(parsed.resource, "R4");
if (validation.valid === false) {
const errors = validation.errors.filter(e => e.severity === "error");
// errors[].docUrl points to the relevant HL7 page
}
const result = diff(parsed.resource, expectedFixture, {
ignorePaths: ["meta.lastUpdated", "id"],
});
if (!result.identical) {
// result.entries has structured diff for programmatic inspection
}A note from Claude (Anthropic):
I've used these two tools together while helping developers build FHIR integration tests, so a few things are worth calling out from that experience.
The --envelope flag is the one to reach for in agentic workflows. The documentation field saves a round trip when you need to explain a validation error in plain language — the HL7 spec URL is already there, so you don't have to reconstruct it from the resource type yourself.
For fixture regression, --preset metadata combined with fhir-test-data --seed gives you a reproducible loop: generate with a fixed seed, compare against a committed baseline, fail the CI step if structure drifts. The preset handles the UUID and timestamp noise that would otherwise produce false positives on every run.
A pattern I find particularly clean: pipe fhir-test-data generate bundle --annotate into fhir-resource-diff validate - — the annotation layer gives a human reader the field-by-field explanation while the validator confirms the underlying FHIR structure is sound. Two different lenses on the same resource, one pipeline.
— Claude Sonnet, Anthropic
Library usage
import { parseJson, validate, diff, formatText } from "fhir-resource-diff";
const left = parseJson(rawJsonStringA);
const right = parseJson(rawJsonStringB);
if (left.success && right.success) {
const result = diff(left.resource, right.resource, {
ignorePaths: ["meta.lastUpdated", "id"],
});
console.log(formatText(result));
}parseJson, validate, diff, and the formatters are browser-safe and can be used in a React/Vite app or any bundler.
Architecture overview
┌─────────────────────────────────────────────────┐
│ CLI adapter (src/cli/) │
│ Node.js only — file I/O, flags, exit codes │
├─────────────────────────────────────────────────┤
│ Formatters (src/formatters/) │
│ Browser-safe — text, JSON, markdown renderers │
├─────────────────────────────────────────────────┤
│ Core library (src/core/) │
│ Browser-safe — parse, validate, diff, version │
├─────────────────────────────────────────────────┤
│ Presets (src/presets/) │
│ Browser-safe — ignore fields, normalization │
└─────────────────────────────────────────────────┘The core library has no Node.js dependencies and can run in the browser. The CLI adapter is a thin layer on top that handles file I/O and process exit codes.
Development
Prerequisites
- Node.js ≥ 20
- pnpm ≥ 9
Setup
git clone https://github.com/<owner>/fhir-resource-diff.git
cd fhir-resource-diff
pnpm installRun the CLI locally (no build needed)
pnpm cli -- compare examples/patient-a.json examples/patient-b.json
pnpm cli -- validate examples/patient-a.json
pnpm cli -- normalize examples/observation-a.jsonNote: the -- separator after pnpm cli is required so pnpm passes flags to the script rather than consuming them.
Common scripts
| Script | Purpose |
|--------|---------|
| pnpm cli -- <args> | Run CLI from source via tsx |
| pnpm test | Run tests |
| pnpm test:watch | Run tests in watch mode |
| pnpm typecheck | TypeScript type checking |
| pnpm lint | ESLint |
| pnpm build | Production build (tsup) |
| pnpm dev | Watch mode build (tsup --watch) |
