I Almost Hand-Wrote a FHIR Schema. Then I Found Out I Didn't Have To. A developer building a FHIR-compliant patient data output for a hospital network abandoned a two-day manual effort to hand-write a Zod schema after discovering that the @aviasole/shapecraft library ships pre-built, validated FHIR R4 schemas. Using the library's PatientSchema, the developer was able to generate compliant resources with a single API call, avoiding the risk of errors that could affect patient records. A hospital-network client wanted our system to output patient data in actual FHIR format - the standard interoperability format healthcare systems use to talk to each other - instead of whatever shape we felt like inventing. Made total sense from their side, their EHR software only accepts FHIR resources, not our custom JSON. From my side, it meant I now had to get an LLM to produce a Patient resource that was FHIR R4 compliant, field for field. I opened the FHIR R4 spec page for Patient to see what I was dealing with. Closed the tab about four minutes later. It's not one flat object - names have their own nested structure with use / family / given arrays, telecom is a list of typed contact points, addresses have their own multi-field shape, and half the fields have specific allowed value sets straight out of a separate FHIR terminology spec. This was not going to be a quick z.object {...} . I started anyway, because what else was I going to do: js const PatientSchema = z.object { resourceType: z.literal "Patient" , identifier: z.array z.object { system: z.string , value: z.string , } , name: z.array z.object { use: z.enum "official", "usual", "nickname", "maiden" , family: z.string , given: z.array z.string , } , telecom: z.array z.object { system: z.enum "phone", "email", "fax" , value: z.string , use: z.enum "home", "work", "mobile" .optional , } , gender: z.enum "male", "female", "other", "unknown" , birthDate: z.string , address: z.array z.object { use: z.enum "home", "work", "temp" .optional , line: z.array z.string , city: z.string , state: z.string , postalCode: z.string , country: z.string , } , // ...and I still hadn't gotten to maritalStatus, communication, // contact, generalPractitioner, managingOrganization... } ; Two days in and I still wasn't done, and worse, I had no real confidence I'd gotten the parts I had written correct. I kept cross-referencing the spec for things like whether gender really only allows those four values or whether I was missing an unknown case, whether telecom.system needed a url variant too, whether I'd nested name right. This was medical data going to an actual hospital system - if I got a field wrong and it silently passed validation because I'd defined it wrong in the first place, that's not a bug someone catches in a code review, that's a bad record in someone's patient chart. That thought alone was enough to make me stop and look for literally any other option before shipping my own guesswork as the source of truth. I was already using shapecraft for the extraction itself, so on a hunch I checked whether anyone had already solved the "FHIR schema in TypeScript" problem as a package rather than a spec page. Shapecraft ships presets for exactly this: js import { PatientSchema } from "@aviasole/shapecraft/fhir"; import { generate, openai } from "@aviasole/shapecraft"; const result = await generate openai { model: "gpt-4o-mini" } , PatientSchema, patientIntakeText ; // result.data is a typed, validated FHIR R4 Patient resource console.log result.data.name 0 .family ; console.log result.data.telecom 0 .system ; Same generate call I already knew from using it elsewhere, same retry/timeout/ guaranteeLevel behavior - I didn't have to learn a new API, just point it at a different schema. The nested name/telecom/address structure I'd been hand-rolling badly for two days was already there, already correct, already typed. Observation , Condition , MedicationRequest , and Encounter were sitting right next to it too, which meant when the client's second request came in "can you also send us the medication list in FHIR" it was a five-minute change instead of another two-day spec-reading marathon. Deleted my hand-rolled PatientSchema entirely - it wasn't even fully correct, I found out later while diffing it against the preset, I'd missed that telecom.system also allows a url / sms / pager variant I'd never gotten to in the spec. Two days of careful, cautious work, still wrong in a spot I hadn't reached yet. What actually shipped was a preset import and the same generate call I'd have written anyway. Kudos to shapecraft for that one - I went from "I might get medical data wrong because I misread a spec doc" to "the schema was already someone else's solved problem" in about ten minutes of searching plus a five-minute swap. If you're staring down a FHIR resource and reaching for a blank Zod schema, check @aviasole/shapecraft/fhir first. It might already be sitting there.