Skip to content

Entity Types in Forms

Overview

Forms can reference custom fields from any entity type (patient, specialist, appointment, organization). The auto-fill and profile sync logic automatically resolves which entity to use based on the custom field's entity_type and the form's appointment context.

How It Works

Form Instance Context

Every form instance has access to all relevant entity IDs:

json
{
  "id": 1024,
  "appointment_id": 500,
  // Derived from appointment:
  "patient_id": 123,        // via appointment.patient_id
  "specialist_id": 45,      // via appointment.specialist_id
  "organization_id": 1      // via RLS context
}

Entity Type Resolution

When auto-filling or syncing, the system uses the custom field's entity_type to determine which entity ID to use:

Custom Field entity_typeEntity ID UsedSource
patientappointment.patient_idPatient profile
specialistappointment.specialist_idSpecialist profile
appointmentappointment.idAppointment metadata
organizationcurrent_org_idOrganization settings

Use Cases by Entity Type

Patient Custom Fields (Most Common)

Purpose: Patient demographics, medical history, preferences

Auto-fill behavior: Reads from patient's profile (shared across all forms)

Profile sync: Updates patient's profile (affects future forms)

Examples:

json
{
  "custom_field_id": 10,  // city (entity_type: patient)
  "sort_order": 1
}

Use cases:

  • Demographics: city, age, birthdate, phone, address
  • Medical history: allergies, blood_type, chronic_conditions
  • Preferences: preferred_language, communication_channel
  • Emergency contact: emergency_contact_name, emergency_contact_phone

Behavior:

  1. Patient fills "city" in Intake Form → "Amsterdam" saved to patient profile
  2. Patient fills Follow-up Form → "city" auto-fills "Amsterdam"
  3. Patient changes to "Rotterdam" → patient profile updated
  4. Next form auto-fills "Rotterdam"

Appointment Custom Fields (Appointment-Specific)

Purpose: Appointment-specific metadata, session notes, internal tracking

Auto-fill behavior: Reads from current appointment's metadata (not from previous appointments)

Profile sync: Saves to current appointment only (doesn't affect other appointments)

Examples:

json
{
  "custom_field_id": 25,  // internal_priority (entity_type: appointment)
  "sort_order": 2
}

Use cases:

  • Metadata: internal_priority, billing_code, referring_doctor, campaign_source
  • Session notes: chief_complaint_today, session_summary, follow_up_needed
  • Internal tracking: no_show_risk, payment_status, requires_translator

Behavior:

  1. Specialist fills "internal_priority" in Appointment Form → "high" saved to appointment #500
  2. Patient books new appointment #501 → "internal_priority" starts empty (new appointment)
  3. Each appointment has its own independent metadata

Note: Appointment-level fields are typically filled during or before the appointment, not auto-filled from previous appointments (since each appointment is unique).


Specialist Custom Fields (Specialist Profile)

Purpose: Specialist credentials, settings, biographical info

Auto-fill behavior: Reads from specialist's profile

Profile sync: Updates specialist's profile

Examples:

json
{
  "custom_field_id": 30,  // license_number (entity_type: specialist)
  "sort_order": 3
}

Use cases:

  • Credentials: license_number, medical_degree, certifications, specialty
  • Settings: consultation_fee, languages_spoken, years_of_experience
  • Bio: education, publications, areas_of_expertise

Behavior:

  • Rarely used in patient-facing forms
  • More common in specialist profile management or admin forms
  • Auto-fills from specialist assigned to the appointment

Example scenario:

  • Form asks specialist to confirm their license number
  • Auto-fills from specialist profile
  • If updated, specialist profile is updated

Organization Custom Fields (Org Settings)

Purpose: Organization-wide configuration, branding, defaults

Auto-fill behavior: Reads from organization settings

Profile sync: Updates organization settings

Examples:

json
{
  "custom_field_id": 40,  // default_language (entity_type: organization)
  "sort_order": 4
}

Use cases:

  • Settings: timezone, default_language, currency, date_format
  • Branding: logo_url, primary_color, terms_of_service_url
  • Defaults: default_appointment_duration, default_consultation_fee

Behavior:

  • Very rare in forms (usually managed via org settings UI)
  • If used, auto-fills from current organization
  • All users in the org see the same values

Mixed Entity Types in One Form

Forms can reference custom fields from multiple entity types in the same form:

Example: Pre-Appointment Questionnaire

json
{
  "title": "Pre-Appointment Questionnaire",
  "fields": [
    {
      "custom_field_id": 10,  // city (patient)
      "label": "City",
      "sort_order": 1
    },
    {
      "custom_field_id": 11,  // blood_type (patient)
      "label": "Blood Type",
      "sort_order": 2
    },
    {
      "custom_field_id": 25,  // internal_priority (appointment)
      "label": "Priority",
      "sort_order": 3
    },
    {
      "custom_field_id": 30,  // specialist_license (specialist)
      "label": "Provider License",
      "sort_order": 4
    }
  ]
}

When form instance is created for appointment #500:

FieldCustom FieldEntity TypeAuto-Fill SourceValue
city#10patientPatient #123 profile"Amsterdam"
blood_type#11patientPatient #123 profile"A+"
internal_priority#25appointmentAppointment #500 metadata(empty - new appointment)
specialist_license#30specialistSpecialist #45 profile"MD-12345"

When form is saved:

  • "city" and "blood_type" → Update patient #123 profile
  • "internal_priority" → Update appointment #500 metadata
  • "specialist_license" → Update specialist #45 profile

Auto-Fill Algorithm

Pseudocode:

javascript
function createFormInstance(template, appointment) {
  const form = {
    appointment_id: appointment.id,
    fields: [],
    values: {}
  }

  for (const templateField of template.fields) {
    if (templateField.custom_field_id !== null) {
      // Snapshot custom field version
      const customField = getCustomField(templateField.custom_field_id)
      const fieldSnapshot = snapshotCustomFieldVersion(customField)
      form.fields.push(fieldSnapshot)

      // Determine entity_id based on entity_type
      const entityId = resolveEntityId(customField.entity_type, appointment)

      // Auto-fill from custom_field_values (if exists)
      const existingValue = db.query(`
        SELECT value FROM custom_field_values
        WHERE custom_field_id = ? AND entity_type = ? AND entity_id = ?
      `, [customField.id, customField.entity_type, entityId])

      if (existingValue) {
        form.values[`field_${customField.id}`] = existingValue
      }
    }
  }

  return form
}

function resolveEntityId(entityType, appointment) {
  switch (entityType) {
    case 'patient':
      return appointment.patient_id
    case 'specialist':
      return appointment.specialist_id
    case 'appointment':
      return appointment.id
    case 'organization':
      return appointment.organization_id
    default:
      throw new Error(`Unknown entity_type: ${entityType}`)
  }
}

Profile Sync Algorithm

Pseudocode:

javascript
function saveFormInstance(form, values) {
  // Update form.values JSONB
  form.values = merge(form.values, values)

  // Sync to custom_field_values
  for (const field of form.fields) {
    if (field.custom_field_id !== null) {
      const fieldKey = `field_${field.custom_field_id}`
      const value = values[fieldKey]

      if (value !== undefined) {
        // Determine entity_id based on entity_type
        const entityId = resolveEntityId(field.entity_type, form.appointment)

        // Upsert to custom_field_values
        db.upsert('custom_field_values', {
          custom_field_id: field.custom_field_id,
          entity_type: field.entity_type,
          entity_id: entityId,
          value: value
        })
      }
    }
  }
}

Common Patterns

Pattern 1: Patient Demographics Form

All patient fields - auto-fill from patient profile, sync back to patient profile

json
{
  "title": "Patient Registration",
  "fields": [
    {"custom_field_id": 10, "sort_order": 1},  // city (patient)
    {"custom_field_id": 11, "sort_order": 2},  // blood_type (patient)
    {"custom_field_id": 12, "sort_order": 3}   // birthdate (patient)
  ]
}

Pattern 2: Session Notes Form

All appointment fields - appointment-specific, no auto-fill from previous appointments

json
{
  "title": "Session Notes",
  "fields": [
    {"custom_field_id": 25, "sort_order": 1},  // chief_complaint (appointment)
    {"custom_field_id": 26, "sort_order": 2},  // session_summary (appointment)
    {"custom_field_id": 27, "sort_order": 3}   // follow_up_needed (appointment)
  ]
}

Pattern 3: Mixed Form

Patient + appointment fields - patient data auto-fills, appointment data starts fresh

json
{
  "title": "Appointment Intake",
  "fields": [
    {"custom_field_id": 10, "sort_order": 1},  // city (patient) - AUTO-FILLS
    {"custom_field_id": 25, "sort_order": 2},  // chief_complaint_today (appointment) - EMPTY
    {"custom_field_id": 11, "sort_order": 3}   // blood_type (patient) - AUTO-FILLS
  ]
}

Edge Cases

Q: What if a patient field is used in a specialist-facing form?

A: It still auto-fills from the patient profile (entity_type determines behavior, not who fills the form).

Example:

  • Specialist fills a form with patient field "blood_type"
  • Auto-fills from patient #123 profile
  • If specialist updates it, patient profile is updated

Q: What if an appointment field is accidentally used across multiple forms in the same appointment?

A: They all read/write to the same appointment metadata.

Example:

  • Form A has appointment field "internal_priority"
  • Form B also has appointment field "internal_priority"
  • Both forms share the same value (from appointment #500 metadata)
  • Updating in one form updates the other

Q: Can a form reference custom fields from multiple patients?

A: No. A form instance is always attached to one appointment, which has one patient.

The patient_id is derived from appointment.patient_id, so all patient fields in that form instance reference the same patient.

Q: What about family/group appointments?

A: Out of scope for v1. Would require:

  • Appointments with multiple appointment_patients (junction table)
  • Forms that specify which patient the field belongs to
  • More complex auto-fill logic

Validation

When creating a form template, the API should validate:

  1. Custom field exists: custom_field_id references a valid published custom field
  2. Organization match: Custom field belongs to the same organization
  3. Entity type compatibility: Warn if entity_type is unusual for the form type
    • Patient-facing forms with specialist fields: ⚠️ Warning
    • Appointment forms with organization fields: ⚠️ Warning