What are FHIR resources? A guide for clinicians and developers

What FHIR resources are, why healthcare teams build on them and what they make possible, with practical Medplum code examples you can try yourself.

Every system has its own idea of a patient

If you’ve ever scoped a healthcare product, you know the first week goes something like this. You sketch a patients table. Then someone asks about guardians, so you add a contacts table. Then lab results arrive, and you discover they come with units, reference ranges, statuses and codes from a vocabulary you’ve never heard of.

Now multiply that by every system you need to talk to. The EHR models a patient one way, the lab another, the payer a third. Integration stops being a feature and becomes a standing project: map their fields to yours, handle the edge cases, repeat for the next partner.

The question worth asking is simple. What if everyone agreed on the shape of the data before you wrote a line of code? That agreement exists. It’s called FHIR, and its building blocks are called resources.

FHIR in one paragraph

FHIR (Fast Healthcare Interoperability Resources, pronounced “fire”) is a standard from HL7 for representing and exchanging healthcare data. It rests on three ideas any web developer will recognize: data is split into well-defined resources, those resources are exposed through a REST API, and they travel as JSON (or XML, if you must). Most production systems today run FHIR R4. That’s the whole elevator pitch; the rest of this article is about why it matters when you’re the one writing the code.

What a resource actually is

A resource is a self-contained, typed record with a defined structure. Think of it as a schema someone else already argued about for years, so you don’t have to. Here’s a minimal Patient:

JSON
{
  "resourceType": "Patient",
  "id": "123",
  "meta": { "versionId": "2", "lastUpdated": "2026-11-01T14:02:00Z" },
  "name": [{ "use": "official", "given": ["Maria"], "family": "Lopez" }],
  "gender": "female",
  "birthDate": "1985-04-12",
  "telecom": [{ "system": "email", "value": "maria@example.com" }]
}

A few things are worth noticing:

  • resourceType tells any system what it’s holding. No guessing from table names.
  • meta carries version and timestamp, so history is built in rather than bolted on.
  • name is an array. People change names, have legal and preferred names, and FHIR planned for that before your first bug report did.

FHIR defines well over a hundred resource types, but most apps live in a handful of families:

Family Common resources
Administrative Patient, Practitioner, Organization, Location
Clinical Observation, Condition, MedicationRequest, AllergyIntolerance
Workflow Appointment, Encounter, Task, ServiceRequest
Financial Coverage, Claim, ExplanationOfBenefit

Two features turn these records into something more useful than a list of tables.

References. Resources point at each other. An Observation says who it’s about with "subject": { "reference": "Patient/123" }. A patient’s record becomes a graph you can traverse and query, not a set of joins you designed at 11 p.m.

Diagram: Encounter, Observation, Condition and MedicationRequest each reference the Patient through their subject field, and Observation also references the Encounter it was taken in.

An Observation can also point to the Encounter it was taken in, so you can ask “what was measured during this visit?” without a custom join.

Coded values. Clinical meaning is carried by codes from standard terminologies, not free text. A heart rate is LOINC 8867-4; a diagnosis might be a SNOMED CT code; a drug, an RxNorm code. Two systems that have never met can agree that they’re talking about the same thing.

Why teams build on resources

A shared vocabulary. When your backend, your frontend, your partner’s EHR and your analytics pipeline all say “Observation,” they mean the same structure. Onboarding a new engineer gets easier too: the spec doubles as documentation.

A data model with clinical mileage. Healthcare data is full of traps: partial dates, multiple identifiers per patient, results that are amended after the fact. FHIR’s resources already account for these because clinicians and implementers ran into them first. Designing your own schema means rediscovering each one in production.

Regulatory tailwinds. In the US, the 21st Century Cures Act and the rules that followed from the Office of the National Coordinator for Health IT (ONC) and CMS push certified health IT and payers toward standardized FHIR APIs for patient and population data access. If your product will ever exchange data with a hospital or payer, FHIR is increasingly the expected interface, not a nice-to-have.

Room for your own needs. No standard covers every product. FHIR handles this with extensions (add a field without breaking the resource) and profiles (constrain a resource for a use case). US Core, for example, is a set of profiles defining what US systems should support. You get flexibility without forking the model.

What resources make possible

Interoperability with far less bespoke mapping. Exchanging data with an EHR, lab or payer becomes “send and receive standard resources” rather than “negotiate a CSV format.” You still map at the edges, but you map once, to the standard.

Search that works the same across compliant servers. FHIR defines search parameters per resource, so this request means the same thing on any compliant server:

Text
GET [base]/Observation?subject=Patient/123&code=http://loinc.org|8867-4&_sort=-date

That returns one patient’s heart rates, newest first and paged like any FHIR search, against whatever base URL the server exposes. No custom endpoint required.

An ecosystem to plug into. SMART on FHIR lets third-party apps launch inside an EHR with the right patient context. Terminology servers, analytics tools and open-source libraries all speak the same format, so you can buy or borrow pieces instead of building them.

History and auditability. Resources are versioned. You can ask what a record looked like last Tuesday, which matters a great deal when a clinician asks “who changed this, and when?”

FHIR in practice with Medplum

Theory is nice; code is more convincing. Medplum is an open-source (Apache 2.0), FHIR-native healthcare platform: a FHIR server, a TypeScript SDK, server-side Bots and a React component library. Your clinical and administrative data is stored as FHIR resources, which makes it a good place to see the ideas above in action.

1. Create a patient. A resource is just typed JSON. @medplum/fhirtypes gives you TypeScript types for every resource, so your editor catches a misspelled field before the server does.

TypeScript
import { MedplumClient, createReference } from '@medplum/core';
import { Patient, Observation } from '@medplum/fhirtypes';

const medplum = new MedplumClient();
await medplum.startClientLogin(process.env.MEDPLUM_CLIENT_ID!, process.env.MEDPLUM_CLIENT_SECRET!);

const patient = await medplum.createResource<Patient>({
  resourceType: 'Patient',
  name: [{ given: ['Maria'], family: 'Lopez' }],
  birthDate: '1985-04-12',
});

2. Record an observation. Note the reference back to the patient, the LOINC code and the standard UCUM unit.

TypeScript
await medplum.createResource<Observation>({
  resourceType: 'Observation',
  status: 'final',
  category: [
    {
      coding: [
        {
          system: 'http://terminology.hl7.org/CodeSystem/observation-category',
          code: 'vital-signs',
        },
      ],
    },
  ],
  code: { coding: [{ system: 'http://loinc.org', code: '8867-4', display: 'Heart rate' }] },
  subject: createReference(patient),
  effectiveDateTime: new Date().toISOString(),
  valueQuantity: {
    value: 72,
    unit: 'beats/minute',
    system: 'http://unitsofmeasure.org',
    code: '/min',
  },
});

3. Search. searchResources wraps standard FHIR search and returns an array. No endpoint to design, no query builder to maintain.

TypeScript
const heartRates = await medplum.searchResources('Observation', {
  subject: `Patient/${patient.id}`,
  code: 'http://loinc.org|8867-4',
  _sort: '-date',
});

4. Automate with a Bot. Bots are server-side TypeScript functions that can run when a resource is created or updated, triggered by a FHIR Subscription. This one turns a high heart rate into a follow-up task for the care team:

TypeScript
import { BotEvent, MedplumClient, createReference } from '@medplum/core';
import { Observation, Task } from '@medplum/fhirtypes';

export async function handler(medplum: MedplumClient, event: BotEvent<Observation>): Promise<void> {
  const obs = event.input;
  const isHeartRate = obs.code?.coding?.some(
    (c) => c.system === 'http://loinc.org' && c.code === '8867-4',
  );
  const bpm = obs.valueQuantity?.value;
  if (!isHeartRate || bpm === undefined || bpm <= 120) {
    return;
  }
  await medplum.createResource<Task>({
    resourceType: 'Task',
    status: 'requested',
    intent: 'order',
    description: `Review elevated heart rate: ${bpm} bpm`,
    for: obs.subject,
    focus: createReference(obs),
  });
}

The output is itself a resource, so it shows up in search, history and the UI with no extra plumbing.

Note: The 120 bpm threshold is illustrative, not clinical guidance.

5. Render with React. Because the UI layer understands resources too, @medplum/react components (inside a MedplumProvider) can display them without custom mapping:

TSX
import { PatientTimeline, ResourceTable } from '@medplum/react';
import { Patient } from '@medplum/fhirtypes';

export function PatientPage({ patient }: { patient: Patient }) {
  return (
    <>
      <ResourceTable value={patient} />
      <PatientTimeline patient={patient} />
    </>
  );
}

The data these snippets create isn’t Medplum-specific. Each create and search maps to the standard FHIR REST API, so the same data is reachable from any FHIR client.

How this simplifies the build

The biggest win is what you no longer have to build. Compare the two paths for a team starting a new clinical product.

Diagram: a custom build goes from a custom schema to a custom API to per-partner mappings, while a FHIR-native build goes from FHIR resources to a FHIR server to a standard FHIR API.

On the custom path, each layer is yours to design and maintain, and every new partner adds another mapping. On the FHIR path, the data model and API are standard, and a FHIR-native platform supplies storage, search, auth, versioning and audit history out of the box. Your engineers spend their time on the workflow that makes your product different.

Where it isn’t magic

FHIR is worth adopting with clear eyes:

  • There’s a learning curve. The spec is large, and concepts like CodeableConcept and profiles take a few weeks to feel natural.
  • “FHIR-compliant” varies. Vendors implement different versions and profiles. Expect to test each integration rather than assuming it just works.
  • Not every workflow maps cleanly. Some product-specific data will live in extensions. That’s fine, but keep it disciplined or you end up with a custom schema in disguise.
  • Terminology is its own project. Choosing and maintaining code systems (LOINC, SNOMED CT, RxNorm) takes deliberate effort.

None of these outweighs starting from a model the rest of the industry already speaks. They just belong in your estimate.

The takeaway

If you’re evaluating a healthcare build, FHIR resources answer the questions you would otherwise spend months on: how to model a patient, how to link clinical data, how to expose it and how to talk to everyone else. Start from resources, add extensions where you genuinely need them, and spend your engineering budget on the parts users will actually notice.

The fastest way to get a feel for it is to create a free Medplum project, post a test Patient and Observation using synthetic data, then run a search. Ten minutes in, the model starts to make sense.

Further reading

Building something in healthcare?

At CodeDuckers, this is the work we do every day. We build EHRs and clinical products both on Medplum and as fully custom, FHIR-based systems, so we can help you pick the path that fits your product rather than the one that fits our toolbox.

If you’re weighing a healthcare build and want a second set of eyes on the data model, the platform choice or the integration plan, we’d be glad to talk it through. No pitch deck required; bring your questions (and your messiest data model).

Get in touch with CodeDuckers →

Written by

· Founder & Software Architect

Experience building software for healthcare, large-scale manufacturing, and other industries. Sets the technical direction for every CodeDuckers project.

Meet the team