Skip to content

Forms

Structured data collection — intake questionnaires, consent forms, assessments, and more — all built from a shared field library with complete immutability once signed.


What this enables

  • Clinics design reusable form templates using a shared field library — define a field once, use it across any form
  • When a patient fills in a field they've answered before (city, blood type, etc.), it's pre-filled automatically from their profile
  • Forms attached to appointments are generated automatically — no manual setup per booking
  • Once a patient signs a form, it becomes legally immutable — no one can alter what was recorded
  • Consent forms automatically create a timestamped legal consent record when signed
  • Specialists can have private fields that are visible during the session but excluded from patient-facing documents

How it works

Forms have two layers:

Templates (design time) — An admin builds a form by selecting fields from the field library, setting their order, and marking some as required or specialist-only.

Instances (runtime) — When an appointment is created, the system generates a form instance from the template. The instance captures a frozen snapshot of every field's definition at that exact moment — so even if the field definition changes later, the historical record is preserved exactly as it was.

Admin designs template (references field library)

Appointment booked → form instance created from template
    → fields snapshot taken at current versions
    → patient's existing profile values auto-filled

Patient fills form (in_progress)
    → saving a field also updates their profile (auto-fill loop)

Patient submits (completed)

Patient signs (signed) — IMMUTABLE from this point
    → if it's a consent form, consent record created automatically

When a form with type='disclaimer' is signed, the platform automatically creates a user_consents entry with:

  • The consent type (e.g., hipaa_notice, video_recording)
  • Timestamp and IP address
  • A link back to the form that was signed

This creates a full legal audit trail without any manual step.

Private fields

Specialists often need to record clinical observations that shouldn't appear in the patient's copy of the document. Fields marked private: true are:

  • Visible to both patient and specialist during form filling
  • Excluded from patient-facing PDFs and documents
  • Always visible to admin and specialist exports

Technical Reference

Everything below is intended for developers.

Architecture

Templates reference custom fields by ID — they don't duplicate field schemas. This is the key architectural decision:

  • Field changes (new options, updated label) propagate automatically to all templates
  • Form instances snapshot the field at the current version at creation time — immutability preserved
  • One-off fields (custom_field_id: null) are supported for form-specific fields that shouldn't sync to profile

Template structure

json
{
  "id": 42,
  "title": "Patient Intake Survey",
  "type": "survey",
  "category": "first_appointment",
  "pdf_template_id": 5,
  "consent_types": [],
  "version": 1,
  "published": true,
  "fields": [
    { "custom_field_id": 10, "sort_order": 1, "required": true },
    { "custom_field_id": 11, "sort_order": 2, "private": true },
    {
      "custom_field_id": null,
      "key": "chief_complaint",
      "type": "textarea",
      "label": "What brings you in today?",
      "sort_order": 3,
      "required": true
    }
  ]
}

Template types & categories

TypeUse Case
disclaimerConsent forms — signing auto-creates user_consents
surveyPatient questionnaires, feedback
parametersClinical measurements
reportAssessment reports
adviceCare recommendations
prescriptionMedication prescriptions
CategoryWhen generated
new_patientFirst-time registration
first_appointmentFirst consultation
new_appointmentRecurring appointments

Template versioning

On publish, a new version is created. Existing form instances are unaffected — they hold a snapshot of the version at creation time. New form instances use the latest published version.

Form instance lifecycle

pending → in_progress → completed → signed (immutable)
  • pendingin_progress: first value saved
  • in_progresscompleted: all required fields filled
  • completedsigned: explicit patient confirmation
  • After signed: values, fields, and files cannot be changed (API returns 409)

Auto-fill flow

  1. Form instance created from template
  2. For each field with a custom_field_id, backend looks up existing custom_field_values for the patient
  3. Found values are pre-filled in form.values
  4. When patient saves values, custom_field_values are upserted for fields with a custom_field_id
  5. Next form with the same field auto-fills from the updated profile

One-off fields (custom_field_id: null) do NOT sync to profile — they're appointment-specific.

Instance snapshot structure

json
{
  "fields": [
    {
      "custom_field_id": 10,
      "version": 2,
      "key": "city",
      "field_type": "text",
      "required": true
    }
  ],
  "values": {
    "field_10": "Amsterdam"
  },
  "files": {
    "consent_signature": {
      "s3_key": "org-1/forms/1024/signature/abc.png",
      "size": 45000
    }
  }
}

Multi-user filling

Both patient and specialist can write to the same form instance. Last write wins (JSONB merge). Each save is recorded in the audit log with the acting user_id. Field-level authorship tracking is deferred to v2.

Files in forms

File fields (signatures, attachments) are uploaded to S3. Access via signed URLs with 15-minute expiry.

Data storage

All form values are stored as plaintext JSONB (not app-level encrypted):

  • Infrastructure encryption (AWS RDS AES-256) satisfies HIPAA at-rest requirement
  • Fully queryable for segments and analytics
  • The private flag controls document visibility, not encryption

API overview

POST   /v1/form-templates               Create template (draft)
GET    /v1/form-templates               List templates
PATCH  /v1/form-templates/{id}          Edit template (sets to draft)
POST   /v1/form-templates/{id}/publish  Publish (increments version)
GET    /v1/form-templates/{id}/versions Version history

POST   /v1/forms                        Create form instance (usually automatic)
GET    /v1/forms/{id}                   Get form
PATCH  /v1/forms/{id}                   Update values (fails if signed)
POST   /v1/forms/{id}/sign              Sign (immutable from here)
POST   /v1/forms/{id}/files             Upload file to form field
GET    /v1/forms/{id}/files/{key}       Get signed URL for file

Audit

All mutations (form.create, form.update, form.sign) are logged with resource_type = "form". Field names are logged — never field values — to ensure no PHI in the audit trail.