Why build on FHIR (and when you shouldn't)

A decision guide for clinician-founders and AI-scribe builders: when FHIR is the right foundation, what it buys you that custom schemas can't, and the honest cases where you should skip it.

Every healthcare app hits the same fork early: invent your own database schema, or model your clinical data as FHIR. The default instinct — "I'll just make a notes table and a patients table" — feels faster. It usually isn't. Here's how to make the call deliberately instead of by accident.

Should you build your healthcare app on FHIR?

Yes, in almost every case. If your app stores clinical data — patients, notes, vitals, assessments, diagnoses — model it as FHIR R4 from day one. You get interoperability, portability, and AI-agent readiness for free, and you avoid rebuilding a worse version of a 150-resource standard yourself. Skip FHIR only for pure-consumer wellness tools that never touch a clinical system.

The custom-schema trap: you'll rebuild FHIR, badly

The seductive shortcut is to design your own tables. It feels lighter. But clinical data is deceptively deep, and you discover the depth one painful migration at a time:

  • A "note" isn't a text column. It has an author, a status (draft → signed → amended), an encounter it belongs to, a patient, a type, and a timestamp that is not the row's created_at.
  • A blood-pressure reading is two numbers, two units, a body site, a method, a "who measured it," and a coded concept — not bp_systolic INT.
  • A diagnosis needs a coding system, a code, a clinical status, a verification status, and an onset that might be a date, an age, or "childhood."
  • An allergy has a criticality, a reaction, a severity, and a "refuted vs. confirmed" state your boolean has_allergy can't hold.

Each of these is a FHIR resource that a standards committee already argued about for years. When you roll your own, you re-litigate every one of those decisions under deadline pressure — and you get them subtly wrong. Six months in, your "simple" schema has accreted a status enum, a versioning hack, a coded-value side table, and a soft-delete column, and you've built FHIR, badly, without the ecosystem. The standard exists precisely so you don't have to.

The honest version: a custom schema doesn't avoid FHIR's complexity — it defers it. You pay later, in migrations, with production data already in the wrong shape.

FHIR is the Series-A expectation

If you plan to raise, get acquired, or sell into a health system, your data model becomes due-diligence material. The questions are predictable: "Can you export a patient's record?" "Can you integrate with an EHR?" "Are you ready for the US interoperability rules?" "How locked-in is your data?" "It's standard FHIR" answers all of them in one sentence. "We have a proprietary schema, but we could write an exporter" is a red flag that turns into a re-platforming line item. Building on FHIR isn't gold-plating — it's the table-stakes answer to questions you will be asked.

FHIR makes you AI-agent-ready

This is the newest and most underrated reason. Agents — scribes, chart-summarizers, prior-auth bots — reason far better over a typed, well-known schema than over your bespoke tables. FHIR resource types are in every model's training data; Observation, Condition, and MedicationRequest carry meaning a model already understands. A custom data_blob JSONB column carries none.

The wedge we care about most is the AI medical scribe. A scribe's whole job is to turn a conversation into structured clinical facts. If those facts land as FHIR, your agent can prep, write, and reconcile against a known shape — and your export, your audit trail, and your interop all come along for free:

// The scribe pipeline, on FHIR primitives
const prep = await clinical.agent.sessionPrep({ patientId });
// → last visit, open problems, active meds, due assessments

await clinical.notes.create({
  patientId,
  text: draftFromTranscript,        // SOAP/DAP, signed by the clinician
});

await clinical.observations.record({
  patientId,
  code: "8480-6",                   // Systolic BP (LOINC)
  value: 128, unit: "mm[Hg]",
});

await clinical.assessments.record("PHQ-9", { patientId, score: 11 });

Under the hood each call writes a FHIR resource — a DocumentReference for the note, an Observation for the vital, a scored QuestionnaireResponse for the PHQ-9. Your agent reasons over standard types; your data stays exportable. (More on the scribe pattern on the AI scribe page.)

What you get for free

Choosing FHIR isn't just defense. It unlocks a real ecosystem you'd otherwise build from scratch:

  • SMART-on-FHIR — the standard OAuth flow to launch your app from inside an EHR, with scoped access. Build to FHIR and you're launch-ready.
  • Apple Health & device data — Apple's HealthKit exports as FHIR. Speak FHIR and patient-generated data flows in without a custom adapter.
  • HIEs and EHRs — health information exchanges and EHRs speak FHIR. Interop becomes a configuration problem, not a rebuild.
  • Validators, terminology, and tooling — public FHIR validators, US Core profiles, and code systems like SNOMED CT (free for US use via the NLM UMLS license), LOINC, and RxNorm. You inherit shared meaning instead of inventing it.
  • A clean export — when an enterprise asks for the patient's record, clinical.fhir.export(patientId) returns a standard Bundle. No exporter project.

When you should NOT use FHIR

An honest guide has to draw the other line too. FHIR is overhead, and sometimes the overhead isn't worth it:

  • Pure consumer wellness with no clinical interop. A meditation timer, a habit tracker, a fitness-streak app that never exchanges data with a provider — modeling a heart-rate log as a FHIR Observation buys you nothing. A plain column is correct.
  • Throwaway prototypes and internal tools. If you're validating a workflow this week and will rewrite the data layer anyway, don't model FHIR yet. Just don't let the prototype quietly become production.
  • Data that genuinely isn't clinical. Billing line items, scheduling preferences, UI state, feature flags, app analytics — these aren't health resources and shouldn't be forced into FHIR. Keep them in normal app tables alongside your clinical layer.
  • Operational scratch state. "Needs review" flags, draft autosave, assignment queues — FHIR has no good home for these, and that's fine. They belong in app state, not the clinical record.

Notice the pattern: skip FHIR for the parts of your app that are not the clinical record. The mistake is the inverse — building the clinical record on a custom schema to "move fast," then paying for it at the worst possible time.

"Build on FHIR" doesn't mean "build directly on a FHIR server"

Here's the nuance that trips teams up. FHIR is the right data model — but a raw FHIR server (HAPI, Aidbox, AWS HealthLake, and others) is an interoperability engine, not an application backend. Build your UI straight against one and you inherit eventually-consistent reads, no home for app state, no built-in access control, and a terminology server to babysit. We cover that gap honestly in FHIR, explained for app developers, and we compare the FHIR servers head-to-head in our backend comparisons.

The pattern that works: FHIR underneath, app-native primitives on top. Your code says "create a note for this patient" and "give me this patient's timeline," with fresh-on-commit reads, real per-patient authorization, and a query you'd actually want — while FHIR R4 resources are generated underneath so you can still export a Bundle and talk to an EHR. The data your app reads stays live and queryable:

// App-native read, FHIR underneath, fresh on commit
const { data: timeline } = useClinicalQuery(
  clinical.notes.list({ patientId, status: "signed" })
);

That's the whole idea behind a clinical backend like bonfireDB's FHIR layer (open-source core, with a managed BAA option, in early access): FHIR is the foundation and the export format, but you don't write FHIR JSON by hand to ship a list screen.

Keep reading

TL;DR

  • Build the clinical record on FHIR. A custom schema doesn't dodge the complexity — it defers it into a painful migration.
  • It's the Series-A expectation. "It's standard FHIR" answers interop, portability, and lock-in due-diligence in one sentence.
  • It makes you AI-agent-ready. Agents reason over standard resource types; your scribe writes structured, exportable facts.
  • You get SMART, Apple Health, HIEs, validators, and SNOMED/LOINC for free.
  • Skip FHIR for non-clinical parts — pure wellness, throwaway prototypes, billing, scheduling, UI/app state.
  • "On FHIR" ≠ "directly on a FHIR server." Use FHIR as the model and export layer; build your app against app-native primitives on top.
FAQ

Frequently asked questions

Should I build my healthcare app on FHIR?

In almost every case, yes. If your app stores clinical data — patients, notes, vitals, assessments, diagnoses — model it as FHIR R4 from day one. You get interoperability, portability, and AI-agent readiness, and you avoid rebuilding a worse version of a 150-resource standard. Skip FHIR only for pure-consumer wellness tools that never touch a clinical system.

Why not just design my own database schema?

A custom schema doesn't avoid FHIR's complexity — it defers it. Clinical data is deceptively deep: a note has status, author, and encounter; a vital is values, units, site, and a code. Roll your own and six months later you've built FHIR, badly, without the ecosystem — and you pay in migrations with production data already in the wrong shape.

Is FHIR a fundraising or due-diligence expectation?

Yes. If you plan to raise, get acquired, or sell into a health system, your data model becomes due-diligence material. Can you export a patient's record? Integrate with an EHR? Are you ready for US interoperability rules? It's standard FHIR answers all of them in one sentence. A proprietary schema is a red flag that becomes a re-platforming line item.

How does FHIR make my app AI-agent-ready?

Agents reason far better over a typed, well-known schema than over bespoke tables. FHIR resource types are in every model's training data — Observation, Condition, and MedicationRequest carry meaning a model already understands; a custom data_blob column carries none. An AI scribe can prep, write, and reconcile against a known shape, with export and audit coming along for free.

When should you NOT use FHIR?

Skip FHIR for the parts of your app that are not the clinical record: pure consumer wellness with no clinical interop, throwaway prototypes you'll rewrite, and genuinely non-clinical data like billing line items, scheduling preferences, UI state, and analytics. Keep those in normal app tables. The mistake is the inverse — building the clinical record on a custom schema to move fast.

Does build-on-FHIR mean building directly on a FHIR server?

No. FHIR is the right data model, but a raw FHIR server is an interoperability engine, not an application backend — you'd inherit eventually-consistent reads, no home for app state, and no built-in access control. The pattern that works: app-native primitives on top, with FHIR R4 generated underneath for export. bonfireDB is built that way, in early access.

You build the app. Bonfire is the clinical data layer underneath.

bonfireDB gives you FHIR R4 underneath and app-native clinical primitives on top — so the foundation is standard, but you ship fast. Open-source core, managed BAA, in early access.