Post Intake Automation | Medplum

On this page

The patient submitted their intake form — now what? This page covers the patterns for triggering intake processing and the workflows that follow once intake data has been ingested into FHIR resources.

This page covers:

Triggering Intake Processing

The most common pattern is a Subscription on QuestionnaireResponse creation that triggers a Bot. When a patient submits their intake form, the Subscription fires and the Bot transforms the response into FHIR resources.

Setting Up the Subscription

Create a Subscription that matches your intake questionnaire. Use the questionnaire search parameter to scope the Subscription to a specific form, so it doesn't fire for every QuestionnaireResponse in your system. Use a create-only subscription (Medplum extension below) so the Bot is not invoked again when the same response is updated — for example after linking subject to a newly created Patient.

{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "Process intake form submissions",
  "criteria": "QuestionnaireResponse?questionnaire=https://example.com/intake-form",
  "channel": {
    "type": "rest-hook",
    "endpoint": "Bot/your-intake-bot-id"
  },
  "extension": [
    {
      "url": "https://medplum.com/fhir/StructureDefinition/subscription-supported-interaction",
      "valueCode": "create"
    }
  ]
}

Avoid Infinite Loops

If your Bot updates the QuestionnaireResponse (e.g., to set subject after creating the Patient), use a create-only Subscription to avoid re-triggering. The intake demo Bot does exactly this — it creates the Patient, then updates the QuestionnaireResponse to link subject to the new Patient. Because the Subscription only fires on creation, the update doesn't re-trigger the Bot.

For more interaction options, see Subscription extensions.

For details on what the Bot does with the response, see Intake Questionnaires: Processing Responses.

Post-Processing Workflows

After the intake Bot creates FHIR resources, you often need downstream workflows. These can be additional Bots triggered by their own Subscriptions, or logic within the intake Bot itself.

Common Post-Intake Actions

Action Trigger Implementation
Notify provider Intake resources created Bot sends a notification (email, SMS, or in-app Communication) to the assigned provider
Flag incomplete data Missing required fields Bot creates a Task assigned to front-desk staff for follow-up
Insurance verification Coverage created Subscription on Coverage triggers a Bot that calls an eligibility API (e.g., Stedi)
Schedule follow-up Specific conditions detected Bot creates an Appointment or Task based on intake answers (e.g., screening referral)
Route to care team Patient assigned to a care program Bot creates Tasks for care team members based on patient characteristics

Task-Based Follow-Up

When intake data requires human follow-up, create a Task to track it:

{
  "resourceType": "Task",
  "status": "requested",
  "intent": "order",
  "description": "Verify insurance information — subscriber ID missing",
  "for": { "reference": "Patient/example" },
  "requester": { "reference": "Bot/intake-bot" },
  "owner": { "reference": "Practitioner/front-desk" }
}

PlanDefinition Orchestration

Optional / Advanced

Many teams process intake with a single Bot triggered on QuestionnaireResponse creation, and that's a perfectly valid approach. PlanDefinition-based orchestration is useful when you need structured, multi-step intake workflows — for example, when intake involves multiple questionnaires completed by different people (patient fills demographics, nurse fills vitals, provider reviews chart).

A PlanDefinition defines a reusable template for a multi-step workflow. When applied with the $apply operation, it automatically creates a CarePlan, RequestGroup, and a set of Task resources — one per action in the plan.

How It Works

  1. Define a PlanDefinition with actions that reference Questionnaires (via definitionCanonical) and optionally ActivityDefinitions for non-questionnaire work (e.g., lab orders)
  2. Call $apply with the patient (and optionally an encounter) as parameters
  3. The server creates Tasks for each action, linked to the patient and encounter

Task Linking

Each Task generated by $apply links to its source Questionnaire in two places — both are required for the provider UI to render correctly:

{
  "resourceType": "Task",
  "status": "requested",
  "intent": "order",
  "focus": { "reference": "Questionnaire/intake-demographics" },
  "encounter": { "reference": "Encounter/visit-001" },
  "for": { "reference": "Patient/example" },
  "input": [{
    "type": { "text": "Questionnaire" },
    "valueReference": { "reference": "Questionnaire/intake-demographics" }
  }]
}

Task Lifecycle

Tasks progress through a standard lifecycle during intake:

Status Meaning
requested Task created, not yet started
in-progress Someone is actively working on the task (e.g., filling out the form)
completed Task finished — for Questionnaire tasks, Task.output[0].valueReference holds the QuestionnaireResponse

When a Questionnaire task is completed, the output references the response:

{
  "output": [{
    "type": { "text": "QuestionnaireResponse" },
    "valueReference": { "reference": "QuestionnaireResponse/response-001" }
  }]
}

PlanDefinition Setup

For PlanDefinitions to work correctly:

For more on PlanDefinitions, see Clinical Decision Support.