> ## Documentation Index
> Fetch the complete documentation index at: https://docs.morf.health/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduling an Appointment for a New Patient via Morf Form

> Embed the Morf scheduling UI inside a Morf Form to let respondents select and book appointments in real time.

> [!WARNING]
>
> If you're integrating with Acuity, contact Acuity support to change your merge setting so that clients are only merged when they share the same first name, last name **and** phone number. Acuity's default merges clients on first name and last name alone.

## Overview

Morf enables respondents to schedule appointments within a Feathery flow through two integration points:

1. **Time Slot Selection Step (iframe)** — Embeds the Morf scheduling UI inside a Feathery step. Displays real-time availability from your scheduling service and allows respondents to select a time slot.
2. **Booking in Progress Step (logic rule)** — Schedules the appointment in your scheduling service using the selected time slot and the respondent's submitted information.

These components are configured independently, allowing flexible form customization. For example, respondents can select a provider and time slot early in the flow, complete insurance or payment details, and confirm the appointment later.

## Prerequisites

Before you begin, confirm you have:

1. A Morf organization.
2. Your branding details shared with Morf (or use default branding).
3. Provider data entered into the Morf provider dashboard.
4. Completed the baseline [Feathery Setup Instructions](https://docs.morf.health/docs/knowledge_base/feathery_setup.md).
5. Feathery fields that collect the following information **before** the appointment is scheduled. Confirm the field IDs match exactly:
   * `first_name`
   * `last_name`
   * `phone_number`
   * `email_address`

## Instructions

### 0. Create a Feathery-triggered workflow in Morf

1. Create your workflow (or use the Morf patient signup template).
2. Configure the **Profile Lookup** node:
   * Lookup field → `user_id`
   * Profile ID → `Profile ID`
3. Configure the **Update Profile Properties** node:
   * Store `user_id` as the Feathery ID.

### 1. Create hidden fields

> [!TIP]
>
> Already included in the Morf patient sign-up template.

In the form, go to **Settings > Data Tracking > Edit Hidden Fields** and add:

* `scheduled_appt`
* `scheduled_appt_id`
* `scheduled_appt_datetime_formatted`
* `appt_type_id`

All fields are populated dynamically during form completion except `appt_type_id`, which must be set in the environment variables logic rule (see step 2).

### 2. Edit logic rule: Set Environment Variables

> [!TIP]
>
> Already included in the Morf patient sign-up template.

Add `appt_type_id` to your existing **Set Environment Variables** custom logic rule. If you haven't created it yet, follow the [Feathery Setup Instructions](https://docs.morf.health/docs/knowledge_base/feathery_setup.md#4-conditionally-set-hidden-field-values).

> [accordion] View code
>
> ```jsx theme={null}
> if (feathery.isTestForm()) {
>   // STAGING
>   morf_org_id.value = 'bffa1f46-e724-481c-8e4d-635728d3da58'; // your staging org id
>   morf_webhook_url.value =
>     'https://api.morf.healthcare/webhooks/8cbe4b21-9922-4e4a-8d4c-0ff74f876c74'; // staging workflow trigger webhook url
>   appt_type_id.value = '40983'; // your appointment type id from Healthie staging
> } else {
>   // PRODUCTION
>   morf_org_id.value = '7c114b98-3604-4e46-92ea-29299184ba78'; // your production org id
>   morf_webhook_url.value =
>     'https://api.morf.healthcare/webhooks/8cbe4b21-9922-4e4a-8d4c-0ff74f876c74'; // production workflow trigger webhook url
>   appt_type_id.value = '40983'; // your appointment type id from Healthie production
> }
> ```

### 3. Create step: Time slot selection

> [!TIP]
>
> Already included in the Morf patient sign-up template.

1. Create a new step in your flow named `Time slot selection`.
2. Add a **Container → iframe** to the step.
3. Set:
   * **Field ID** → `time_slot`
   * **Source URL** → Use the scheduling URL from the [direct scheduling link documentation](https://docs.morf.health/docs/morf_questions/overview.md).
4. Optionally hide filters by appending query parameters:
   * `hideState`
   * `hideInsurance`
   * `hideSpecialties`
5. Recommended iframe height: `85vh`

### 4. Create logic rule: Initialize time slot selection

> [!TIP]
>
> Already included in the Morf patient sign-up template.

This rule listens for messages from the Morf scheduling UI iframe and stores the selected time slot details: `providerId`, `timezone`, `appointmentTypeId`, and `datetime`.

1. Go to **Logic > Create New Logic Rule**.
2. Name the rule `Initialize time slot selection`.
3. Configure the trigger:
   * **Event trigger:** Form Step is Loaded
   * **Step trigger:** `time_slot_selection`
4. Go to **Advanced Logic > Edit Code** and add the following. Update `goToStep` to use the step name that should follow time slot selection.

> [accordion] View code
>
> ```jsx theme={null}
> window.addEventListener('message', function (event) {
>   console.log('Received message from iframe:', event.data);
>
>   if (event.data && event.data.payload.datetime) {
>     time_slot.value = event.data.payload;
>     time_slot_datetime.value = event.data.payload.datetime;
>     time_slot_provider_id.value = event.data.payload.providerId;
>     time_slot_appt_type_id.value = event.data.payload.appointmentTypeId;
>     time_slot_timezone.value = event.data.payload.timezone;
>     feathery.goToStep('REPLACE WITH STEP ID TO FOLLOW TIME SLOT SELECTION');
>   }
> });
> ```

### 5. Create intermediate steps (optional)

Insert any steps to collect additional patient information — such as insurance or credit card details — before scheduling the appointment.

### 6. Create booking status steps

> [!TIP]
>
> Already included in the Morf patient sign-up template.

Create three steps:

1. **Booking in progress** — Triggered when the respondent views this step; initiates the appointment booking in your third-party service. Recommended styling: loading icon + "Booking your appointment…" message.
2. **Appointment confirmed** — Displayed when the appointment is successfully booked. Use the `scheduled_appt` field to display booking details.
3. **Booking error** — Displayed if the scheduling service returns an error. Recommended: instruct the user to retry time slot selection.

The `response.data.message` field will contain one of the following error values:

| Error | Cause |
| - | - |
| `time slot taken` | The selected time slot is no longer available. |
| `invalid email or account with email already exists` | A patient with the submitted email already exists in Healthie. |
| *(any other error)* | Likely a network issue. Prompt the user to check their connection and retry from time slot selection. |

### 7. Add logic rule: Schedule appointment

> [!TIP]
>
> Already included in the Morf patient sign-up template.

1. Go to **Logic > Create New Logic Rule**.
2. Name the rule `Schedule appointment`.
3. Configure the trigger:
   * **Event trigger:** Form Element is Viewed
   * **Element trigger:** an element on your **Booking in Progress** step
4. Go to **Advanced Logic > Edit Code** and add the following. Update the `goToStep` calls at the bottom to match your booking error and appointment confirmation step names.

> [accordion] View code
>
> ```jsx theme={null}
> // create the request body for the scheduling endpoint
> const body = {
>   "appointmentTypeId": time_slot_appt_type_id.value,
>   "datetime": time_slot_datetime.value,
>   "emailAddress": email_address.value,
>   "firstName": first_name.value,
>   "lastName": last_name.value,
>   "morfOrgId": morf_org_id.value,
>   "patientId": feathery.userId,
>   "phoneNumber": phone_number.value,
>   "providerId": time_slot_provider_id.value,
>   "schedulingApplication": "HEALTHIE",
>   "timezone": time_slot_timezone.value,
> }
>
> const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
>
> const formatDatetime = (datetime, timezone) => {
>   const appt_date = new Date(datetime);
>   const dateTimeFormat = new Intl.DateTimeFormat('en-US', {
>     weekday: 'long',
>     month: 'long',
>     day: 'numeric',
>     hour: 'numeric',
>     minute: 'numeric',
>     hour12: true,
>     timeZone: timezone
>   });
>   return dateTimeFormat.format(appt_date);
> }
>
> // make POST request to Morf's scheduling endpoint
> await delay(200);
> const response = await feathery.http.post("https://questions.morf.health/api/scheduleAppointment", body);
> console.log("Scheduled appointment response: ", response.data);
>
> // store scheduling response
> scheduled_appt_error.value = response.data.message;
> scheduled_appt.value = response.data;
>
> // build webhook payload to send to Morf
> const webhook_payload = {
>   "scheduled_appt": scheduled_appt.value,
>   "stripe_token_id": stripe_token_id.value,
>   "feathery_form_id": feathery.formId,
>   "feathery_form_name": feathery.formName,
>   "feathery_step_id": "Booking in progress",
>   "feathery_user_id": feathery.userId,
> }
>
> // send Booking in progress step event to Morf
> try {
>   await feathery.http.post(morf_webhook_url.value, webhook_payload);
> } catch {
>   console.error("Could not deliver booking in progress webhook");
> }
>
> // navigate respondent to correct step
> if (response.data.message) {
>   feathery.goToStep('Booking error');
> } else {
>   scheduled_appt_id.value = response.data.id;
>   scheduled_appt_datetime_formatted.value = formatDatetime(time_slot_datetime.value, time_slot_timezone.value);
>   feathery.goToStep('Appt confirmed');
> }
> ```

### 8. Add Morf API connectors

> [!TIP]
>
> Already included in the Morf patient sign-up template.

Add two API connectors in Feathery:

1. **Morf scheduling endpoint** — Used to schedule the appointment in your scheduling service.
   * URL: `https://questions.morf.health/api/scheduleAppointment`
   * Method: POST

2. **Morf webhook URL** — Used to send the "Booking in progress" step event to Morf.
   * URL: `https://api.morf.healthcare/webhooks/...`
   * Method: POST


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.