fhir-test-data is a TypeScript library and CLI for generating valid FHIR R4/R4B/R5 test
resources with country-aware identifiers. This showcase demonstrates what it produces across
locales, resource types, bundle wiring, and edge cases.
The CLI writes to stdout by default. Pipe it into jq, validators, load testing tools, or
AI assistants — no configuration needed.
# Extract identifiers with jq
fhir-test-data generate patient --locale uk --seed 42 | jq '.identifier[0]'
# Stream NDJSON into a validator
fhir-test-data generate patient --count 100 --format ndjson | your-fhir-validator --stream
# POST to a FHIR server
fhir-test-data generate patient --locale nl --seed 1 | \
curl -s -X POST https://your-fhir-server/Patient \
-H "Content-Type: application/fhir+json" -d @-
# Hand off to an AI assistant to inspect or explain
fhir-test-data generate bundle --locale kr --seed 5 | \
llm "summarise the clinical findings in this FHIR bundle"A United Kingdom patient includes an NHS Number — a 10-digit identifier validated with Modulus 11. Every generated NHS Number passes the check-digit algorithm:
fhir-test-data generate patient --locale uk --count 1 --seed 42{
"resourceType": "Patient",
"id": "3f2a1b4c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
"identifier": [
{
"system": "https://fhir.nhs.uk/Id/nhs-number",
"value": "4857326190"
}
],
"name": [
{
"use": "official",
"family": "Whitfield",
"given": ["Oliver"],
"prefix": ["Mr"]
}
],
"telecom": [
{ "system": "phone", "value": "+44 20 7946 0932", "use": "home" },
{ "system": "email", "value": "o.whitfield@example.co.uk" }
],
"gender": "male",
"birthDate": "1981-07-14",
"address": [
{
"use": "home",
"line": ["42 Pemberton Road"],
"city": "Manchester",
"postalCode": "M1 3AX",
"country": "GB"
}
],
"communication": [
{ "language": { "coding": [{ "system": "urn:ietf:bcp:47", "code": "en-GB" }] } }
]
}The NHS Number 4857326190 passes Modulus 11 — 97 - ((4×10 + 8×9 + 5×8 + 7×7 + 3×6 + 2×5 + 6×4 + 1×3 + 9×2) mod 11) = 0.
The same seed with different locales produces locale-correct identifiers, names, and addresses:
# Dutch patient — BSN passes 11-proef
fhir-test-data generate patient --locale nl --seed 42
# French patient — NIR (social security) with Modulus 97 check key
fhir-test-data generate patient --locale fr --seed 42
# Indian patient — Aadhaar with Verhoeff check digit
fhir-test-data generate patient --locale in --seed 42
# German practitioner — LANR (doctor registration number) with Modulus 10
fhir-test-data generate practitioner --locale de --seed 42
# South Korean patient — RRN with gender-encoded digit
fhir-test-data generate patient --locale kr --seed 42
# Brazilian patient — CPF with Modulus 11 variant check
fhir-test-data generate patient --locale br --seed 42
# South African patient — SA ID number with Luhn check
fhir-test-data generate patient --locale za --seed 42
# Singaporean patient — NRIC / FIN with check letter
fhir-test-data generate patient --locale sg --seed 42All generated identifiers pass the official check-digit algorithm for their country.
The bundle builder composes all resource types into a single FHIR Bundle with automatic
reference wiring. Every internal reference uses urn:uuid: format and is consistent:
fhir-test-data generate bundle --locale au --seed 1 --output ./fixtures/fixtures/
Bundle-0001.json
The generated bundle contains:
- 1 Patient (with IHI number, Luhn-validated)
- 1 Organization (hospital)
- 1 Practitioner (with HPI-I number, Luhn-validated)
- 1 PractitionerRole (linking practitioner to organization)
- 3–5 clinical resources (Observations, Conditions, AllergyIntolerance, MedicationStatement)
All references are wired:
Patient.managingOrganization→ OrganizationObservation.subject→ PatientObservation.performer[0]→ PractitionerCondition.subject→ Patient
Observations use real LOINC codes — blood pressure, BMI, glucose, haemoglobin — with
valueQuantity in clinically plausible ranges and HL7-consistent units of measurement
(mm[Hg], kg/m2, mg/dL, g/dL). Conditions use SNOMED CT codes. AllergyIntolerance
resources include coded substances.
# Generate an observation and inspect the LOINC code and units
fhir-test-data generate observation \
--locale uk --seed 10 | jq '{code: .code.coding[0], value: .valueQuantity}'The same seed always produces the same output, regardless of when or where it runs:
fhir-test-data generate patient --locale uk --seed 42 > a.json
fhir-test-data generate patient --locale uk --seed 42 > b.json
diff a.json b.json
# (no output — files are identical)This makes seeded output reliable for snapshot tests, golden file comparison, and regression test fixtures. Pair with fhir-resource-diff to verify that generated fixtures remain stable across library upgrades:
# Regenerate fixtures
fhir-test-data generate bundle --locale us --seed 1 --output ./fixtures/
# Compare against committed baseline
fhir-resource-diff compare ./fixtures/Bundle-0001.json ./baseline/Bundle-0001.jsonimport { createPatientBuilder, createBundleBuilder } from "fhir-test-data";
// Deterministic patient for unit test
const [patient] = createPatientBuilder().locale("nl").seed(99).build();
expect(patient.identifier[0].value).toMatch(/^\d{9}$/); // BSN format
// Bundle with deep-merged metadata override
const [bundle] = createBundleBuilder()
.locale("us")
.seed(42)
.type("transaction")
.clinicalResourcesPerPatient(5)
.overrides({ meta: { source: "test-suite-v1" } })
.build();
expect(bundle.resourceType).toBe("Bundle");
expect(bundle.entry.length).toBeGreaterThan(5);All builders target any FHIR version. R4 is the default:
# R5 bundle — MedicationStatement emitted as MedicationUsage
fhir-test-data generate bundle --locale us --seed 1 --fhir-version R5
# R4B (structurally identical to R4 for all generated resources)
fhir-test-data generate patient --locale de --fhir-version R4BIn R5, two resources change shape:
MedicationStatement→MedicationUsage: type renamed,medicationCodeableConceptrestructured tomedication.conceptAllergyIntolerance.type: plain code string →CodeableConceptwithcodingarray
The fhir-test-data/faults submodule generates intentionally invalid resources for testing
validation pipelines, error handlers, and rejection behaviour:
import { createPatientBuilder } from "fhir-test-data";
import { withFault } from "fhir-test-data/faults";
// Generate a patient with a missing required field
const [invalidPatient] = withFault(
createPatientBuilder().locale("uk").seed(42),
"missing-resource-type"
).build();
// Test that your validation pipeline catches it
const result = validate(invalidPatient);
expect(result.valid).toBe(false);
expect(result.errors[0].path).toBe("resourceType");Generate thousands of records for load testing or seed data:
# 1000 US patients as NDJSON for bulk loading
fhir-test-data generate patient \
--locale us \
--count 1000 \
--format ndjson \
--output ./fixtures/
# Mixed locale bundle set
for locale in us uk au de fr nl jp kr; do
fhir-test-data generate bundle \
--locale $locale \
--count 10 \
--seed 1 \
--output "./fixtures/$locale/"
doneGenerate and validate fixtures in one pipeline step:
- name: Regenerate test fixtures
run: fhir-test-data generate bundle --locale us --seed 1 --output ./fixtures/
- name: Validate generated fixtures
run: |
for f in fixtures/*.json; do
fhir-resource-diff validate "$f" --fhir-version R4
done