Schema Care

FHIR QuestionnaireResponse for Intake Forms, Step by Step

By Schema CarePublished Updated

A FHIR QuestionnaireResponse for intake forms records one patient's answers to a form that a Questionnaire resource defines. Give each printed question a stable linkId, store each answer in the value type its question declares, keep the response in-progress while any answer awaits review, then extract Patient, AllergyIntolerance and other resources once it is complete.

This post is about modeling the data yourself. Schema Care writes no QuestionnaireResponse, offers no API, SDK, webhooks, FHIR, X12 or 275 output, and does not read insurance cards. The intake section, patient and codes below are a made-up example, and the server name fhir.example.com is a placeholder.

Questionnaire and QuestionnaireResponse for an intake form

An intake form needs two FHIR resources. The Questionnaire defines the form once: its questions, their order, their answer types and allowed choices. The QuestionnaireResponse holds one patient's filled-in copy and points back to that Questionnaire. You write the Questionnaire when you design the form, then create one QuestionnaireResponse per completed packet.

The HL7 FHIR R4 QuestionnaireResponse page says questions may be included directly or by reference to a Questionnaire, which also sets the constraints on allowed answers. It adds that a response can be validated against its Questionnaire to check that required groups and questions were answered. Without the Questionnaire, a receiving system sees answers but cannot check them.

The response has a few top-level elements worth setting on every intake packet:

ElementCardinalityIntake use
questionnaire0..1Canonical URL and version of the form
status1..1in-progress, completed, amended, entered-in-error or stopped
subject0..1The Patient the form is about
authored0..1When the answers were gathered
author0..1Who recorded the answers, such as staff keying the form
source0..1Who gave the answers, such as the patient or a parent
item0..*The groups and questions

The R4 notes say clinicians may author a response with answers provided by someone else, which fits paper intake well. The patient is the source; whoever transcribed or reviewed the paper is the author.

Assigning linkIds to a paper intake form's questions

A linkId is the stable identifier that ties each question in a Questionnaire to its answer in a QuestionnaireResponse. Give every printed section and question its own linkId once, when you model the form, and never reuse or renumber it when the paper layout changes. Responses collected years apart then still line up.

The HL7 FHIR R4 Questionnaire page defines linkId as an identifier unique within the Questionnaire that links each item to the equivalent item in a QuestionnaireResponse. The response page adds that the response's item hierarchy SHALL mirror the Questionnaire, and that the order of questions within groups must be kept.

Practical rules for paper forms:

  • Name sections as groups. A printed "Patient information" box becomes a group item with child questions.
  • Use readable, versionless IDs. demo.dob survives a reprint; q7 breaks when a question is added above it.
  • Do not encode the page number. Layout moves; meaning does not.
  • Give repeated blocks one linkId. A list of allergies is one repeating question, not five numbered ones.
  • Keep a crosswalk. Map each linkId to the printed label and its position on each version of the paper form.

A six-question intake section, as an example

The fragment below is a fictional Questionnaire section with six questions. It is shortened to the elements that matter here.

{
  "resourceType": "Questionnaire",
  "url": "https://fhir.example.com/Questionnaire/ex-adult-intake",
  "version": "2026-03",
  "status": "active",
  "item": [{
    "linkId": "demo",
    "type": "group",
    "text": "Patient information",
    "item": [
      { "linkId": "demo.name", "type": "string", "text": "Full legal name" },
      { "linkId": "demo.dob", "type": "date", "text": "Date of birth" },
      { "linkId": "demo.contact", "type": "choice", "text": "Preferred contact method",
        "answerOption": [
          { "valueCoding": { "system": "https://fhir.example.com/CodeSystem/contact-method", "code": "phone", "display": "Phone" } },
          { "valueCoding": { "system": "https://fhir.example.com/CodeSystem/contact-method", "code": "text", "display": "Text message" } },
          { "valueCoding": { "system": "https://fhir.example.com/CodeSystem/contact-method", "code": "email", "display": "Email" } }
        ] },
      { "linkId": "hx.allergy.any", "type": "boolean", "text": "Do you have any drug allergies?" },
      { "linkId": "hx.allergy.list", "type": "text", "text": "If yes, list them", "repeats": true },
      { "linkId": "consent.sms", "type": "boolean", "text": "I agree to receive appointment reminders by text" }
    ]
  }]
}

Which QuestionnaireResponse answer types fit intake checkboxes, dates and free text?

Each QuestionnaireResponse answer uses the value type that matches its question's item type. A yes-or-no checkbox is valueBoolean, a date is valueDate, a pick-one list is valueCoding, and a written answer is valueString. The Questionnaire sets the type, and the response follows it.

The HL7 FHIR R4 item type code system states the pairing for each type:

Item typeAnswer elementIntake example
booleanvalueBooleanA single yes or no checkbox
datevalueDateDate of birth
choicevalueCodingOne box ticked from a printed list
open-choicevalueCoding or valueStringA list with an "Other: ____" line
stringvalueStringName, a few words
textvalueStringA multi-line answer
integervalueIntegerNumber of pregnancies, years at an address
attachmentvalueAttachmentA clipped scan of a drawn signature

Here is the matching fictional QuestionnaireResponse for Jordan Example. One answer, the handwritten allergy list, is held for review, so the response stays in-progress:

{
  "resourceType": "QuestionnaireResponse",
  "id": "ex-qr-jordan",
  "questionnaire": "https://fhir.example.com/Questionnaire/ex-adult-intake|2026-03",
  "status": "in-progress",
  "subject": { "reference": "Patient/ex-pat-jordan" },
  "authored": "2026-10-14T09:12:00-05:00",
  "source": { "reference": "Patient/ex-pat-jordan" },
  "item": [{
    "linkId": "demo",
    "item": [
      { "linkId": "demo.name", "answer": [{ "valueString": "Jordan Example" }] },
      { "linkId": "demo.dob", "answer": [{ "valueDate": "1991-07-30" }] },
      { "linkId": "demo.contact", "answer": [{ "valueCoding": {
          "system": "https://fhir.example.com/CodeSystem/contact-method", "code": "text", "display": "Text message" } }] },
      { "linkId": "hx.allergy.any", "answer": [{ "valueBoolean": true }] },
      { "linkId": "hx.allergy.list" },
      { "linkId": "consent.sms", "answer": [{ "valueBoolean": true }] }
    ]
  }]
}

Three habits keep the answers clean. Convert printed dates to YYYY-MM-DD before writing them. Store the code, not the label, for a choice. Never write valueBoolean: false for a checkbox that was simply left blank, because a blank box is not the same as "no". Which history answers should stay free text instead of being forced into codes is covered in the section on history questionnaire answers that should stay free text.

A consent checkbox on an intake form is first an answer, so record it as a boolean item in the QuestionnaireResponse. Create a separate Consent resource only when another system must act on it as a rule, such as a privacy preference controlling who may see data. The Consent can then point back to the response as its source.

The HL7 FHIR R4 Consent page names four uses: privacy, treatment, research and advance care directives. It says only the privacy use case is modeled in R4. Its source[x] element can reference a QuestionnaireResponse, so the paper answer and the computable rule stay linked. The same page says consent signatures are recorded in the Provenance resource, not in Consent itself.

That gives a simple decision:

Checkbox on the formWhere it belongs
"I agree to text reminders"A boolean item; your messaging system reads it
"I acknowledge receipt of the privacy notice"A boolean item
"Do not share my records with ____"A boolean item, plus a privacy Consent that cites the response
A signature linePresence noted on the form; the signed image kept as the source document

Do not treat a Consent resource as a stronger record than the form. Both depend on reading the checkbox correctly. How software decides whether a box is checked, and what to do when a mark is ambiguous, is in the guide to reading consent checkboxes and signatures on intake forms.

Extracting Patient and AllergyIntolerance from a QuestionnaireResponse

Extraction turns a completed QuestionnaireResponse into ordinary FHIR resources such as Patient and AllergyIntolerance. The HL7 Structured Data Capture guide defines the mechanisms. Observation-based extraction makes only Observations from coded questions. Definition-based extraction ties questions to elements of any resource, so it is the usual route for demographics and allergies.

The SDC form data extraction page, in SDC version 4.0.0 (STU 4), the current published release, describes four approaches:

ApproachHow it works, in plain termsFit for intake
Observation-basedEach question carries an item.code, usually LOINC, plus the observationExtract extension; answers become Observation valuesMeasurements and coded yes or no answers
Definition-basedQuestions point via item.definition to elements in a StructureDefinition; a definitionExtract extension marks which group creates which resourcePatient name and birth date, AllergyIntolerance
Template-basedA template resource supplies boilerplate, and answers fill its gapsFixed-shape resources
StructureMap-basedA mapping language transforms the whole responseComplex forms with a mapping team

The page notes that the older itemExtractionContext extension is deprecated in favor of definitionExtract. It also says observation-based and definition-based extraction always produce a transaction Bundle, which you then post to the server.

For the example, definition-based extraction would map demo.name and demo.dob to Patient.name and Patient.birthDate, and each entry under hx.allergy.list to a new AllergyIntolerance. The extractor copies the patient's words into the allergy record. It does not decide what the allergy means; mapping text to codes is a separate, reviewed step.

Two SDC rules matter in production. Extraction only makes sense once a response is completed, because the data for valid resources may not be there before. And the Provenance for each extracted resource SHOULD point back to the source response, so anyone can trace a Patient field to the form answer and the scan behind it.

Before posting the Bundle, match the response to the right chart. Wrong-patient writes are the costliest mistake in this pipeline, and the checks are covered in the section on how to match an extracted document to the right EHR patient. If your EHR takes registration fields rather than FHIR, the section on mapping intake form fields to EHR registration fields covers that route, and the wider guide to mapping extracted fields to an EHR covers the rest of the write path.

Low-confidence intake answers in a QuestionnaireResponse

A low-confidence intake answer should never be written into a QuestionnaireResponse as if it were certain. Leave that item present with no answer, keep the response status in-progress, and send the answer to a person who can see the original page. Fill the answer and set completed only after review.

The R4 page supports this pattern. It says items SHOULD be included in the response if they are relevant to interpreting the answers, and that this can include items that were not enabled, which will have no answers. An item present without an answer is therefore a shape the specification expects. The in-progress status signals that the response is not final, and SDC's rule that extraction follows completion keeps the uncertain answer out of Patient and AllergyIntolerance until then.

In the example, the handwritten allergy list was the uncertain item. The flow:

  1. Read the page. The extractor reads "Penicilin" with a confidence of 0.64, under an example threshold of 0.90.
  2. Hold it. The response is saved with hx.allergy.list present and unanswered, and status in-progress.
  3. Review it. A person sees the handwriting highlighted on the scan and confirms the patient wrote "Penicillin".
  4. Complete it. The answer is added as valueString, the status becomes completed, and extraction runs.

Do not fill the gap from the boolean above it. A "yes" to "any drug allergies?" says an allergy exists; it does not say which.

Paper intake forms reach this point through a reading step. Schema Care reads patient intake forms, covering demographics, insurance, history and consent checkboxes, and gives every field a confidence score, so a smudged answer goes to your team rather than into the record, as the human review workflow shows. It stops there and writes no FHIR. Supporting documents that later travel with a claim use a different standard, explained in the guide to X12 275 claim attachments, and copying many stored responses out of a server at once is covered by FHIR bulk data export.

All posts in APIs, FHIR and Integration