Third-Party FHIR Renderers¶
quickq's default delivery tool is quickq-forms — bundled when you install quickq, used by quickq serve and quickq preview. But the contract between quickq and any delivery tool is FHIR R4, not a quickq-specific API. Any tool that accepts a FHIR Questionnaire and produces a FHIR QuestionnaireResponse works without modification.
This page covers when you'd use a third-party tool, how to preview your instrument in one, and what quickq tests to keep that contract honest.
When to use a third-party renderer¶
Three common cases:
- You already use REDCap / Qualtrics / a clinical EHR portal. Your IRB, your respondents, or your institution mandate a specific tool. Hand them the FHIR Questionnaire JSON; collect the responses; import them via
quickq fhir import-response. - You need a custom mobile app for in-field data collection. Build it against the FHIR R4 spec; quickq doesn't care what's in front.
- You want to demonstrate interop to a regulator, a collaborator, or yourself — "this study's data model is portable, not locked to one tool." Render the FHIR export through NLM's reference renderer to make the contract concrete.
If none of these apply, stick with quickq-forms. It's the renderer with the best fidelity to what quickq actually emits (correctly honors the slider, ranked-with-ordinalValue, grid, and skip-logic extensions; see the renderer-coverage audit for the empirical comparison).
Preview through LHC-Forms¶
LHC-Forms is the reference FHIR Questionnaire renderer maintained by the U.S. National Library of Medicine. It's the closest thing to a "reference implementation" of the FHIR Questionnaire spec.
To preview your instrument through it:
quickq preview study.db 1 --renderer=lhc-forms
This opens a localhost server that bundles LHC-Forms (cached at ~/.cache/quickq/lhcforms/ after first download) and renders your exported FHIR Questionnaire. Inputs are interactive but no responses are saved — it's purely a visual / interop check.
What you'll see:
- Simple types (single_choice, multiple_choice, boolean, text, numeric, date, datetime, likert) render correctly. The FHIR contract is honored end-to-end.
- Sliders fall back to a plain text input. LHC-Forms doesn't currently honor the
itemControl=sliderextension or the min/max metadata. The question is still answerable but you lose the affordance. - Ranked questions render as a single-select combobox. LHC-Forms doesn't currently honor the
ordinalValueextension or surface a multi-position ordering UI. Respondents can pick one option but cannot rank them. - Grids render correctly as horizontal tables.
- Skip logic (
enableWhen,enableBehavior=all|any) works.
These limitations are LHC-Forms gaps, not quickq bugs — the FHIR export is correct, LHC-Forms just doesn't render every detail. If you need real slider/ranked affordances, use quickq-forms (or your custom renderer).
Static HTML export¶
To produce a single-file HTML page that renders your instrument anywhere with a browser — useful for emailing a preview to a collaborator who doesn't have quickq installed:
quickq preview study.db 1 --output instrument-preview.html
The file uses LHC-Forms loaded from the NLM CDN. No server required to view; just open the HTML.
REDCap¶
REDCap supports importing FHIR Questionnaires. Workflow:
quickq fhir export study.db 1 --output instrument.json
# in REDCap: Project Setup → Import a FHIR Questionnaire → upload instrument.json
When responses come back from REDCap (REDCap exports FHIR QuestionnaireResponse JSON):
quickq fhir import-response responses.json study.db
We haven't end-to-end-tested every REDCap variant. If you hit a parser warning, check the data_quality_flag table — quickq writes warnings rather than throwing, so partial imports are recoverable.
Custom mobile / web clients¶
If you're building a custom renderer (React Native, Flutter, a clinical portal frontend), the contract is:
| Direction | What you send / receive |
|---|---|
| In | FHIR R4 Questionnaire JSON from quickq fhir export |
| Out | FHIR R4 QuestionnaireResponse JSON to quickq fhir import-response |
quickq's quickq-forms is itself a reference implementation of this — it's open source and you can fork it, study its serializer for the exact shape of nested items, repeating groups, grids, and ranked answers, or just import the quickq_forms.engine.fhir_serializer module to skip writing your own.
The key invariants:
- Repeating groups: emit N separate top-level items with the same
linkId, one per instance. Children inside each parent item are scoped to that instance. - Grids: emit a parent item with the grid's
linkIdand a nesteditem[]where each child is a row's response (linkIds suffixed.r0,.r1, …). - Ranked: emit
valueCodinganswers in rank order, each with anextensionof typehttp://hl7.org/fhir/StructureDefinition/ordinalValuecarrying the rank asvalueDecimal. - Skip-logic exclusion: do not include answers for disabled items in the output. quickq's importer treats their absence as "structurally missing" (the question was hidden), not "truly missing" (the respondent skipped a visible question).
How quickq keeps the interop story honest¶
Two test suites guard the FHIR contract:
quickq-forms/tests/e2e/test_render_round_trip.py(28+ tests) — the product correctness suite. Verifies quickq-forms renders every question type and round-trips throughimport_fhir_responseback into the OLTP. Runs on every commit.quickq/tests/test_e2e_lhcforms.py(35 tests) — the interop canary. Verifies the FHIR JSON exported by quickq renders correctly in LHC-Forms. Runs before any FHIR-export change ships and nightly in CI. Selectable aspytest -m interop.
Both suites use the same fixture set. A passing quickq-forms test with a failing LHC-Forms test on the same fixture is the signal "our FHIR shape is wrong but our renderer hides it" — which has caught real bugs (most recently a 2026-05-12 fix where enableWhen answer types were always emitted as answerString regardless of the trigger's actual type).
For the full status grid of which features are verified in which renderer, see renderer-coverage.md (internal).