Morf uses a small configuration language designed to enable powerful and type-safe configurations of Workflows, it’s called Common Expression Language (CEL for short). In Morf we use it for many of the core features of the workflow builder, such as: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.
- Controlling branching (filters) through boolean expressions.
- Defining duration of pauses through expressions that return durations.
- Computing calculated values like
first_name + " " + last_namewith simple operators and custom functions.
Workflow Configuration Expression Syntax
Source events for workflows have defined object definitions. From these definitions we are able to build a WorkflowContext which contains the core data in a Workflow Execution. Some examples of contexts would be things like, a Formsort Form Response (JSON payload) or a Healthie Appointment (fetched by Morf in response to receiving a Healthie Appointment webhook).
Fields from these objects are accessible in a CEL expression by simply referencing the field, for example:
appointment_datetime field.
Nested objects are indexed using familiar .-syntax, for example in a Formsort Form Response:
? modifier described in the next section.
Optionals
Morf workflow configurations make explicit the difference between a value being not present (missing / undefined / null) and the empty or “default” value, for example"" for an empty string, 0 for an integer value.
Optionals subscribe to the “functor” typeclass interface, so you can map
over these values.
Constructing an Optional value
Constructing an Optional value
optional.of() Wraps a given value in an optional.Examples:Constructing a missing Optional value (None / `null`)
Constructing a missing Optional value (None / `null`)
optional.none Returns the none value (represents “null”).Checking a value is specified at runtime
Checking a value is specified at runtime
optional.hasValue() Returns a boolean at runtime, true if specified, false if none / nullGetting the value if specified with a backfup default value
Getting the value if specified with a backfup default value
optional.orValue("default") Returns the value if specified or the default value if not.If the default value’s type is different to the optional’s value, the type
information will be thrown away and become “dynamic” meaning typechecking may
yield different results.
Mapping over an optional value
Mapping over an optional value
optional.optMap(value, value + " example extra suffix text") Maps over a value, operating on the bound variable (e.g. value here), to yield a value within the optional result.Chaining / sequencing operations on optional values
Chaining / sequencing operations on optional values
optional.optFlatMap(value, optional.of("foo").optMap(x, + x + " example extra suffix text")) Maps over a value, operating on the bound variable (e.g. value here) with a function that also returns an optional value.? modifier before the field being indexed into. For example for a Formsort Form Response event payload, where variables aren’t necessarily always defined for all steps of a Formsort flow, you could access the value safely as follows:
? check to yield the underlying value inside the optional.
Numericals
CEL supports only 64-bit integers and 64-bit IEEE double-precision floating-point. Note that the integer 7 as an int is a different value than 7 as a uint, which would be written 7u. Double-precision floating-point is also supported, and the integer 7 would be written 7.0, 7e0, .700e1, or any equivalent representation using a decimal point or exponent. The following operators are supported:| Operator | Description |
|---|---|
- (unary) | Negation |
* | Multiplication |
/ | Division |
% | Remainder |
+ | Addition |
- (binary) | Subtraction |
== != < > <= >= | Relations |
Workflow Data Variables
Variables that are always available in the Workflow configuration environment at runtime regardless of the event payload data type or source.morf_event_type
A string representation of the Source event type that triggered the workflow."HEALTHIE_FORM_ANSWER_GROUP_CREATED""VITAL_LAB_ORDER_RESULTS_UPDATE"
morf_formatted_event_type
A string representation of the formatted Source event type that triggered the workflow, designed for display to the end user (for example in analytics rollups)."Healthie Form Answer Group Created""Vital Lab Order Results Update"
morf_object_type
The ID of the object type that triggered the workflow. This is the identifier for the Source third party object that triggered the workflow."6123512"
morf_profile_ids
The matched Profiles associated third party IDs. When a Workflow discovers or creates a new Profile, its associated active and merged IDs are available with this object.morf_profile_ids.customer
This is an automatically generated V4 UUID identifier that Morf associates
with a freshly created Profile.
morf_profile_ids.formsortmorf_profile_ids.healthiemorf_profile_ids.axle_healthmorf_profile_ids.butterfly_labsmorf_profile_ids.recurlymorf_profile_ids.intercommorf_profile_ids.sana_benefitsmorf_profile_ids.active_campaignmorf_profile_ids.junctionmorf_profile_ids.segmentmorf_profile_ids.intakeqmorf_profile_ids.customer_iomorf_profile_ids.freshdeskmorf_profile_ids.hubspotmorf_profile_ids.stripemorf_profile_ids.featherymorf_profile_ids.open_phonemorf_profile_ids.elationmorf_profile_ids.athenamorf_profile_ids.posthogmorf_profile_ids.nextechmorf_profile_ids.medplummorf_profile_ids.sprucemorf_profile_ids.zohomorf_profile_ids.boulevard
You may discover a Profile during a workflow’s profile lookup process using a
merged ID, but the active
id value will always represent the most recently
updated ID of that third party type that Morf has processed.morf_event_time
morf_event_time: timing.v1.Timestamp representation of the triggering event’s timestamp.
Profile Properties
Profile properties are accessible within CEL expressions using the following macro.get_current_property_value
Fetches the current value of a patient profile property at the time this node executes.- Dynamic: The value is fetched fresh each time the node runs, not snapshotted at workflow start
- Retry-aware: On node retries, this may return a different value if the profile was updated externally between attempts
Event Time and Time Now
morf_event_time Returns the time of the event snapshot, i.e. the time the event that triggered the source to send Morf data occurred at.
morf.now() Returns the current time.
Strings
Functions that operate on values of the typestring a UTF-8 string representation.
As a general note, all indices are zero-based.
charAt
Returns the character at the given position. If the position is negative, or greater than the length of the string, the function will produce an error:emailAddSubaddress
Adds a subaddress to an email address.titleCase
Converts a string to title case, replacing underscores with spaces and capitalizing each word.stripPrefix
Removes the specified prefix from a string.urlEncode
URL encodes a string for safe use in URLs.regexReplaceAll
Replaces all matches of a regular expression with a replacement string.regexFindAll
Finds all matches of a regular expression in a string and returns them as a list.zipCodeExists
Checks if a US zip code exists and is valid.toJsonString
Converts a value to a JSON string representation. Supports pretty printing.parseInt
Parses a string to an integer. Falls back to parsing as float if direct integer parsing fails.parseUrlQuery
Parses a string as a URL and returns a map of query parameters where each key maps to a list of values. The values are automatically URL-decoded.format
Returns a new string with substitutions being performed, printf-style. The valid formatting clauses are:-
%s- substitutes a string. This can also be used on bools, lists, maps, bytes, Duration and Timestamp, in addition to all numerical types (int, uint, and double). Note that the dot/period decimal separator will always be used when printing a list or map that contains a double, and that null can be passed (which results in the string “null”) in addition to types. -
%d- substitutes an integer. -
%f- substitutes a double with fixed-point precision. The default precision is 6, but this can be adjusted. The stringsInfinity,-Infinity, andNaNare also valid input for this clause. -
%e- substitutes a double in scientific notation. The default precision is 6, but this can be adjusted. -
%b- substitutes an integer with its equivalent binary string. Can also be used on bools. -
%x- substitutes an integer with its equivalent in hexadecimal, or if given a string or bytes, will output each character’s equivalent in hexadecimal. -
%X- same as above, but with A-F capitalized. -
%o- substitutes an integer with its equivalent in octal.
%b) is considered an error, as well as attempting
to use more formatting clauses than there are arguments (%d %d %d while passing two ints, for instance).
If compile-time checking is enabled, and the formatting string is a constant, and the argument list is a literal,
then letting any arguments go unused/unformatted is also considered an error.
indexOf
Returns the integer index of the first occurrence of the search string. If the search string is not found the function returns -1. The function also accepts an optional position from which to begin the substring search. If the substring is the empty string, the index where the search starts is returned (zero or custom).join
Returns a new string where the elements of string list are concatenated. The function also accepts an optional separator which is placed between elements in the resulting string.lastIndexOf
Returns the integer index at the start of the last occurrence of the search string. If the search string is not found the function returns -1. The function also accepts an optional position which represents the last index to be considered as the beginning of the substring match. If the substring is the empty string, the index where the search starts is returned (string length or custom).lowerAscii
Returns a new string where all ASCII characters are lower-cased. This function does not perform Unicode case-mapping for characters outside the ASCII range.regexReplaceAll
Returns the first string arguments’ segments matching the second string argument (a valid POSIX regular expression) with the third argument string.replace
Returns a new string based on the target, which replaces the occurrences of a search string with a replacement string if present. The function accepts an optional limit on the number of substring replacements to be made. When the replacement limit is 0, the result is the original string. When the limit is a negative number, the function behaves the same as replace all.reverse
Returns a new string whose characters are the same as the target string, only formatted in reverse order. This function relies on converting strings to rune arrays in order to reversesplit
Returns a list of strings split from the input by the given separator. The function accepts an optional argument specifying a limit on the number of substrings produced by the split. When the split limit is 0, the result is an empty list. When the limit is 1, the result is the target string to split. When the limit is a negative number, the function behaves the same as split all.strings.quote
Introduced in version: 1 Takes the given string and makes it safe to print (without any formatting due to escape sequences). If any invalid UTF-8 characters are encountered, they are replaced with \uFFFD.stripPrefix
Removes a prefix from a string.substring
Returns the substring given a numeric range corresponding to character positions. Optionally may omit the trailing range for a substring from a given character position until the end of a string. Character offsets are 0-based with an inclusive start range and exclusive end range. It is an error to specify an end range that is lower than the start range, or for either the start or end index to be negative or exceed the string length.titleCase
Converts a string to title case.trim
Returns a new string which removes the leading and trailing whitespace in the target string. The trim function uses the Unicode definition of whitespace which does not include the zero-width spaces. See: https://en.wikipedia.org/wiki/Whitespace_character#UnicodeupperASCII
Returns a new string where all ASCII characters are upper-cased. This function does not perform Unicode case-mapping for characters outside the ASCII range.urlEncode
Encodes a string for use in a URL.randomNumbers
Generatesn random digits as a string.
This function is not safe for cryptographic purposes.
shorten_link
Creates a shorter link to a URL. This is particularly useful when sending SMS messages to patients. The shortened URL will be of the form:https://api.morf.healthcare/l/aB1cD2.
To use your own domain you need the following:
- A new subdomain created on your DNS configuration for short links. e.g.
links.mycompany.com. - A load balancer to redirect requests from your custom subdomain to
api.morf.healthcare.
"https://my.very.long.url/to/be/shortened".shorten_link("links.mycompany.com"), you would need a load balancer configured to redirect links.mycompany.com to api.morf.healthcare. Here is an example configuration for the popular tool nginx:
uuidV4
Generates a random UUID v4 string.Collections
Functions that operate on collections.Lists or Maps (Objects / Dictionaries)
all
Tests whether a predicate holds for all elements of a list or keys of a map.exists
Likeall, but tests whether a predicate holds for any element.
exists_one
Tests whether a predicate holds for exactly one element.map
Transforms a list by running an expression on each element. Or transforms a map by running an expression on each key and returning a list. An optional second argument can be passed and runs as a filter.filter
Filters a list by running an expression on each element. Or filters a map by running an expression on each key and returning a list.in
Checks if an element exists in a list. Or if a key exists in a map.size
Returns the size of a list or map.Lists
take
Takes the first n elements from a list.drop
Drops the first n elements from a list, returning the remaining elements.Maps (Objects / Dictionaries)
flattenMaps
Flattens a list of maps into a single map. Later maps override earlier ones for duplicate keys.merge
Merges 2 maps, the right-hand side map takes precedence.deleteKey
Deletes a key from a map.Extra functions on other datatypes
Functions that we’ve added to CEL.Timestamps and Civil Dates
parseTimestamp
Parses a timestamp string into the timestamp type. If only one argument is provided, the string is expected to be in the RFC3339 format"2006-01-02T15:04:05Z07:00" or the following date format "2006-01-02". If two arguments are provided, the first argument is the Golang format of the string to parse, and the second argument is the string to parse.
We recommended reading https://pkg.go.dev/time#Layout if you have to define
date/timestamp formats for displaying or parsing. It uses a reference date to
construct a format rather than the more common approach
YY/MM/dd HH:mm:ss.parseDate
Parses a date string into the date type. If only one argument is provided, the string is expected to be in the"2006-01-02" format. If two arguments are provided, the first argument is the Golang format of the string to parse, and the second argument is the string to parse.
We recommended reading https://pkg.go.dev/time#Layout if you have to define
date/timestamp formats for displaying or parsing. It uses a reference date to
construct a format rather than the more common approach
YY/MM/dd HH:mm:ss.parseDuration
Parses a duration string into the duration type.formatDateUSStyle
Formats av1.values.Date OR v1.timing.Timestamp OR string to a US style date string MM/DD/YY.
When used on a string, the only accepted input formats are
2006-01-02 or RFC3339 format "2006-01-02T15:04:05Z07:00".formatSimpleLocalDatetimeWithTimezone
Formats av1.timing.Timestamp OR string value as a simple local datetime with timezone using the given ISO timezone location string. e.g. Monday, January 27th at 10:30am PST.
this method is also available on the
string type, it expects the string to
be in the RFC3339 format "2006-01-02T15:04:05Z07:00".customFormatInTimezone
Formats av1.timing.Timestamp value as a string with the given format and ISO timezone.
customFormatWithTimezoneOffsetSeconds
Formats av1.timing.Timestamp value as a string with the given format and timezone offset in seconds east of UTC. This function is useful when you have a numeric timezone offset in seconds rather than a named timezone, for example on a Healthie Appointment event payload.
getAge
Calculates the age, given a birth date (timing.v1.Timestamp OR values.v1.Date OR string value) on the left handside and an optional date on the right handside.
String arguments need to either be a date in the format
"2006-01-02" or a
timestamp string in RFC3339 format "2006-01-02T15:04:05Z07:00".nextWeekdayTime
Calculates the time at the next weekday after the first argument (timing.v1.Timestamp OR values.v1.Date OR string value) .
String arguments need to either be a date in the format "2006-01-02" or a timestamp in the RFC3339 format "2006-01-02T15:04:05Z07:00".
getDate
Returns the date of a timestamp.getWeekDay
Returns the day of the week as a string.getYearDay
Returns the day of the year.getMilliseconds
Returns the UNIX epoch milliseconds of a timestamp.getDateString
Returns the date as a string. e.g.2022-03-08
formatTimeStringInTimezone
Formats a timestamp as a time string in the given timezone. e.g.9:00PM.
formatDateStringInTimezone
Formats a timestamp as a date string in the given timezone. e.g.February 2, 2024.
abs_diff
Calculates the absolute duration difference between two timestamps.isAfter
Checks if a timestamp is after another timestamp. Or if a date is after another date.isBefore
Checks if a timestamp is before another timestamp. Or if a date is before another date.modDuration
Calculates the modulus of two durations.getYear
Returns the year of a timestamp/date.getMonth
Returns the month of a timestamp/date.getDayOfMonth
Returns the day of a timestamp/date.add
Adds a duration to a date.sub
Substracts a duration from a date.toString
Formats a date as a string. e.g. 2022-03-08Zip Codes (and Timezones)
zipCodeToTimezone
Parses a 5-digit US zipcode as a IANA database timezone, e.g.America/New_York. Note: most EHRs that accept timezones accept this standard form of timezone.
zipCodeToState
Parses the first three characters of a US zipcode, e.g.902 from 90210 and returns the upper-cased 2-letter US state abbreviation, for example "CA".
See example zip code mappings listed here.
Phone Numbers
Functions that operate on phone number strings to format and validate them using international phone number standards.formatPhoneNumber
Formats a phone number value or a phone number string according to the specified format. Supports both US and international phone numbers. This function will error if passed a phone number string which is invalid - for example an empty string:"".
"E164"- International format without separators (e.g.,+15551234567)"INTERNATIONAL"- International format with separators (e.g.,+1 555-123-4567)"NATIONAL"- National format for the country (e.g.,(555) 123-4567)"RFC3966"- RFC3966 standard format (e.g.,tel:+1-555-123-4567)
- The input string is not a valid phone number
- The phone number cannot be parsed
Real Examples
| Example | Description | Used in |
|---|---|---|
staffer_users[?0].?properties.?healthie_dietician_id.orValue("451492") | Gets the first dietician ID from the staffer users list, or returns the fallback ID “451492” if none is present. | Value |
answers.?current_step_id.orValue("") == "appointment-booking-inprogress" && answers.?scheduled_appt.?id.hasValue() | Passes only when the form’s current step matches the expected booking step AND a scheduled appointment ID was returned — confirms the booking completed successfully. | Filter |
!get_current_property_value("lifecyclestage").hasValue() | Passes only when the patient’s lifecycle stage profile property has never been set. Useful for one-time onboarding workflows that should not re-run. | Filter |
entries.metric_entries.size() == 0 | Passes only when a patient has no metric entries yet — use to trigger an initial data-collection step for new patients. | Filter |
morf_event_type.contains("SIGNED") | Passes only when the Healthie event is a document-signing event (e.g., a provider signed a note or a patient signed a consent form). | Filter |
form_answers[?"21234567"].hasValue() ? morf.now().isBefore(parseTimestamp("2006-01-02", form_answers["21234567"].answer).add(parseDuration("96h"))) : true | Passes if the Healthie form answer for question “21234567” exists and is less than 96 hours old. Returns true (passes) if the answer doesn’t exist yet, so the workflow proceeds on first run. | Filter |
!["Program Hold","Discharge","Ineligible","Lost","Test"].exists_one(grp, morf.get_current_property_value("user_group_name").orValue("").contains(grp)) | Passes only when the patient’s user group name does NOT match any of the excluded values. Use to prevent a workflow from running on discharged, ineligible, or test patients. | Filter |
morf_profile_ids.intercom.?id.hasValue() | Passes only when the patient already has an Intercom contact ID, ensuring downstream Intercom actions have a valid target. | Filter |
(current_step_id.orValue("") == "Insurance Details" || finalized == true) && answers.?insurance_member_id.hasValue() | Passes when a Formsort responder has reached or completed the Insurance Details step AND provided an insurance member ID — use to trigger insurance verification mid-form. | Filter |
get_current_property_value("phone_number").hasValue() || get_current_property_value("email_address").hasValue() | Passes when the patient has at least one contact method on file. Use before sending an outreach message to avoid no-op actions. | Filter |
(morf_event_type.contains("SCHEDULED") || morf_event_type.contains("UPDATED")) && appointment_type_title.orValue("").contains("Prescriber Evaluation") | Passes when a Healthie appointment was booked or rescheduled AND the appointment type name includes “Prescriber Evaluation”. Use to start a prescriber-specific workflow. | Filter |
answers.?appointment_confirmed.orValue(false) && answers.?scheduled_appt.?id.hasValue() | Passes when a Formsort form response confirms the appointment and contains a valid appointment ID — distinguishes a completed booking from an abandoned or in-progress one. | Filter |
morf_event_type.contains("SCHEDULED") && appointment_type_title.orValue("").contains("Initial") | Passes when a Healthie appointment was booked AND the appointment type contains “Initial” — use to trigger a new-patient onboarding workflow only on first appointments. | Filter |
form_completed == true && step_id == "legal" | Passes when a Formsort form was fully completed at the “legal” step — use to trigger actions only after legal consent is submitted, not on mid-form autosaves. | Filter |
appointment_notes.orValue("").indexOf("Zocdoc") >= 0 ? true : false | Passes when the appointment notes contain the string “Zocdoc”. Use to detect referral source from free-text appointment notes. | Filter |
profile_ids.?healthie.hasValue() | Passes only when the profile has a Healthie patient ID. Use to ensure the patient exists in Healthie before running Healthie actions. | Filter |
referring_physicians.size() > 0 | Passes only when the Healthie patient has at least one referring physician on file. Use to gate actions that require a referring provider. | Filter |
tags.exists_one(x, x == "Important") | Passes only when the Calendly lead has a tag matching “Important”. | Filter |
finalized | Returns true when a Formsort form has been fully submitted. Use as a Filter condition to only proceed on complete responses, not partial autosaves. | Filter |
appointment_datetime.sub(parseDuration("72h")) | Returns a timestamp 72 hours before the appointment — use as the “wait until” time to fire a pre-appointment reminder. | Wait node |
appointment_datetime.add(parseDuration("90m")) | Returns a timestamp 90 minutes after the appointment starts — use as the “wait until” time to fire a post-visit follow-up. | Wait node |
morf_event_type.contains("NO_SHOW") ? morf.now() : morf.now().add(parseDuration("24h")) | Returns now if the appointment was a no-show (no delay), or 24 hours from now otherwise — use to fire a follow-up immediately on no-shows and the next day otherwise. | Wait node |
appointments.filter(a, a.appointment_status == "Confirmed" && a.appointment_datetime.isAfter(morf.now()))[0].appointment_datetime | Returns the datetime of the first upcoming confirmed appointment from a list — use as the “wait until” time to delay until the patient’s next appointment. | Wait node |
staffer_users[?0].?properties.?healthie_dietician_id.orValue("451492") | Gets the first dietician ID from the staffer users list, falling back to a default ID. | Value |
appointment_first_name.orValue("") + " " + appointment_last_name.orValue("") | Builds a full name string from the appointment’s own first and last name fields. Note: the appointment may carry a different name than the profile (e.g., a parent booking for a child). | Value |
first_name.orValue("") + " " + last_name.orValue("") | Builds a full name string from the event’s top-level first and last name fields (e.g., from a Healthie user event). | Value |
get_current_property_value("first_name").orValue("") + " " + get_current_property_value("last_name").orValue("") | Builds a full name from stored profile properties rather than event fields — use when the event may not carry name data directly. | Value |
appointment_id + "|" + morf.get_current_property_value("session_id").orValue("") | Builds a composite deduplication key from the appointment ID and session ID, separated by a pipe. Useful for idempotent event tracking in analytics systems. | Value |
double(form_answers["31111111"].answer) | Converts the Healthie form answer for question ID “31111111” to a double (number). Required when passing a numeric form answer to a calculation or to an action field that expects a number. | Value |
scheduled_event.?cancellation.?reason.orValue("No cancellation reason") | Returns the cancellation reason from a Calendly scheduled event, or the string “No cancellation reason” if none was provided. | Value |
email.lowerAscii() | Lowercases the email address from the event. Use when setting an email property to avoid duplicate profiles from case differences (e.g., in HubSpot or Intercom). | Value |
answers.?phone_number.or(answers.?phone_number_sp) | Returns the primary phone number answer if present, otherwise falls back to the Spanish-language equivalent field. Use when a form has both English and Spanish field variants. | Value |
get_current_property_value("date_of_birth").optMap(d, formatDateUSStyle(d)).orValue("") | Formats the date-of-birth profile property in US date format (MM/DD/YYYY), defaulting to an empty string if absent. | Value |
parseTimestamp(morf_event_time.add(parseDuration("720h")).getDateString() + "T00:00:00Z").getMilliseconds() | Adds 30 days to the event time, snaps to midnight UTC, and returns the result in milliseconds. Use for HubSpot close date fields, which expect a millisecond timestamp. | Value |
answers.?is_day_time_patient.optMap(val, val == 'true' ? 'No' : 'Yes') | Inverts a boolean string form answer (“true”/“false”) to a display label (“No”/“Yes”). Use when the field’s logical meaning is the opposite of its stored value. | Value |
answers.?time_slot_appt_type_id.orValue("") == "382952" ? "Psychiatry" : answers.?time_slot_appt_type_id.orValue("") == "382864" ? "Psychotherapy" : "Other" | Maps a form answer appointment type ID to a human-readable label using chained ternary expressions. Use when downstream systems need a label rather than an internal ID. | Value |
healthie_user.diagnoses.map(d, d.icd_code.?code.orValue("")).join(", ") | Extracts all ICD codes from a Healthie patient’s diagnoses array and joins them as a comma-separated string. | Value |
healthie_user.?date_of_birth.optMap(dob, dob.getAge(morf.now())) | Computes the patient’s current age in years from their Healthie date of birth. | Value |
autoscored_sections.filter(s, s.section_title == "Total Score")[0].value | Finds the autoscored section titled “Total Score” in a Healthie form response and returns its numeric value. | Value |
morf.now().getDateString() | Returns today’s date as an ISO string (e.g., “2024-01-15”). Use when setting a “last contacted” or “enrolled on” date property. | Value |
morf.now().add(parseDuration("24h")).getDateString() | Returns tomorrow’s date as an ISO string. Use when setting a follow-up date property one day out. | Value |
answers.?drugs.orValue([]).filter(x, x != "Something else").join(", ") + (answers.?drugs_something_else.hasValue() ? ", " + answers.?drugs_something_else.orValue("") : "") | Filters the “Something else” placeholder from a multi-select array, joins the real values, and appends the free-text override field if the patient filled it in. | Value |
morf_profile_ids.stripe.?id.hasValue() ? 1 : 0 | Returns 1 if the patient has a Stripe customer ID, 0 otherwise. Use for CRM systems that expect a number instead of a boolean. | Value |
form_answers[?"38844177"].answer.orValue("").regexReplaceAll("<[^>]+>", "").trim() | Strips HTML tags from a Healthie form answer and trims surrounding whitespace. Use when a rich-text answer needs to be stored or sent as plain text. | Value |
morf_event_type.lowerAscii().stripPrefix("healthie_") | Converts a Healthie event type to lowercase and removes the “healthie_” prefix, producing a clean event name for analytics (e.g., healthie_APPOINTMENT_SCHEDULED → appointment_scheduled). | Value |
answers.?sex.optMap(s, s.titleCase()) | Title-cases the sex field value from a form answer (e.g., “male” → “Male”). Use when the downstream system expects title-cased values. | Value |
morf.now().customFormatInTimezone("2006-01-02T15:04:05Z07:00", "UTC") | Formats the current timestamp as an ISO 8601 string in UTC using Go-style layout. Use when an action field expects an exact datetime string format. | Value |
active_tags.size() > 0 ? dyn(active_tags.join(";")) : dyn(null) | Joins an array of Healthie active tags into a semicolon-delimited string, or returns null if the array is empty. dyn() is needed because the field type varies between string and null. | Value |
step_id.replace("- ", "").replace(" ", "_") + "_submitted" | Normalizes a Formsort step ID to a snake_case analytics event name by stripping dashes and replacing spaces (e.g., “Step 1 - Intake” → “Step_1_Intake_submitted”). | Value |
form_answers["15956392"].answer.split(" - ")[0] | Extracts the first segment of a Healthie form answer that encodes multiple values separated by ” - ” (e.g., a score paired with a label). | Value |
appointment_datetime.formatSimpleLocalDatetimeWithTimezone(healthie_user.timezone) | Formats an appointment timestamp in the patient’s own stored timezone as a human-readable string (e.g., “Monday, March 3rd at 10:30am EST”). Use in reminder messages. | Value |
answers.merge({"event_timestamp_milliseconds": morf_event_time.getMilliseconds(), "is_final_form_response": finalized}) | Adds computed fields to the Formsort answers map before passing it to downstream actions. Use to enrich the payload without modifying the original form data. | Value |
morf_event_payload.deleteKey("dietitian_email_address").deleteKey("has_created_password").deleteKey("money_owed") | Strips sensitive Healthie fields from the event payload before forwarding it to an external system like an analytics pipeline or CRM. | Value |
get_current_property_value("insurance_type").orValue("[]").regexReplaceAll(r'\[|\]|"', '') | Converts a JSON-array-formatted profile property (e.g., ["PPO","HMO"]) to a plain comma-separated string by stripping the bracket and quote characters. | Value |
toJsonString(false, providers) | Serializes a list or map to a compact JSON string. Use when an action field expects a JSON-encoded value rather than a native object. | Value |
form_answers.filter(a, form_answers[a].answer != "").map(a, {a: form_answers[a].answer}).flattenMaps() | Collects all non-empty Healthie form answers into a single flat map of question ID to answer string. Use to forward a clean answer set to a downstream action. | Value |
appointment_add_to_cal_link.optMap(atcl, atcl.parseUrlQuery()[?"location"][?0]).orValue("") | Extracts the location query parameter from a Healthie appointment’s calendar invite link URL. | Value |
primary_phone_number.optMap(x, x.formatPhoneNumber("E164")) | Formats an optional phone number in E.164 format (e.g., “+16175551234”), propagating absent if the field is missing. Use when sending SMS or storing a phone number for Twilio/Intercom. | Value |
has(hubspot_v1_get_contact_result_0.properties.referral_appointment_attended) && hubspot_v1_get_contact_result_0.properties.referral_appointment_attended != "" ? string(int(hubspot_v1_get_contact_result_0.properties.referral_appointment_attended) + 1) : "1" | Increments a HubSpot string-typed counter field by reading it as an integer, adding 1, and converting back to string. Returns “1” if the property is not yet set. Use has() (not ?) here because HubSpot properties are concrete struct fields, not CEL optionals. | Value |
