Editor
Lexical rich-text editor with Cal variable insertion for workflows, event types, and profiles.
Cross-domain product editor used for workflow reminder bodies, event descriptions, private notes, and profile bios. Callers own form persistence; the Feature control owns Lexical composition and optional Cal variables.
import { AddVariablesDropdown, Editor } from "@calcom/features/components/editor";
import type { BookingFieldVariable, TextEditorProps } from "@calcom/features/components/editor";Ownership
Feature — not @coss/ui/shared. Lives under packages/features/components/editor because it depends on Lexical, Cal variable nodes/plugins, markdown lifecycle helpers, and domain variable catalogs. Reuse the Feature import; do not relocate for docs and do not reimplement Lexical plugins in product or documentation.
Use when
- A Cal surface needs the shared rich-text (or plain-text SMS) editor with the standard toolbar.
- The caller may pass workflow/event variables and optional booking-field variables.
- The surrounding form owns
getText/setTextpersistence and dirty state.
Do not import Lexical plugins directly or document internal plugin files as product patterns. Import Editor (and AddVariablesDropdown when inserting variables outside the editor toolbar).
Canonical usage
Workflow reminder bodies exercise the widest public surface — variables, height, toolbar exclusion, plain text, and editable gating:
<Editor
addVariableButtonTop={isSms}
editable={editable}
excludedToolbarItems={isSms ? ["blockType", "bold", "italic", "link"] : []}
firstRender={firstRender}
getText={() => textRef.current || ""}
height="200px"
plainText={isSms}
setFirstRender={setFirstRender}
setText={(text: string) => {
textRef.current = text;
onChange(text);
}}
updateTemplate={false}
variables={variables}
/>This matches production. Keep Lexical internals, variable node rendering, and markdown transformers inside Editor.
Booking-field variables
Private note templates also pass bookingFieldVariables for attendee custom fields:
<Editor
getText={() => md.render(formMethods.getValues("privateNoteTemplate") || "")}
setText={(value: string) => {
const cleanedValue = value.replace(/ /g, " ");
const markdownValue = turndown(cleanedValue);
formMethods.setValue("privateNoteTemplate", markdownValue, { shouldDirty: true });
}}
variables={[
"event_type_title",
"event_duration",
"attendee_name",
"attendee_first_name",
"organizer_name",
"organizer_first_name",
"location",
]}
bookingFieldVariables={privateNoteBookingFieldVariables}
placeholder={t("private_note_template_placeholder")}
height="100px"
firstRender={privateNoteFirstRender}
setFirstRender={setPrivateNoteFirstRender}
/>Standalone variable dropdown
Use AddVariablesDropdown next to a plain input when variables are inserted outside the editor toolbar (for example email subject in the same workflow step):
<AddVariablesDropdown
addVariable={addVariableToSubject}
variables={DYNAMIC_TEXT_VARIABLES}
/>API
| Prop | Type | Notes |
|---|---|---|
getText | () => string | Current HTML/markdown text from the form |
setText | (text: string) => void | Writes editor output back to the form |
excludedToolbarItems | string[] | Hide defaults: blockType, bold, italic, link |
variables | string[] | Token names available from the toolbar dropdown |
bookingFieldVariables | BookingFieldVariable[] | Custom booking-field tokens |
addVariableButtonTop | boolean | Places the variables control above the toolbar row |
height / maxHeight | string | Editor surface sizing |
placeholder | string | Empty-state placeholder |
disableLists | boolean | Strips list markdown shortcuts |
updateTemplate / firstRender / setFirstRender | lifecycle | Template reload wiring used by workflows |
editable | boolean | Defaults to true; disables toolbar when false |
plainText | boolean | SMS-style plain text mode |
label | string | Optional visible label above the editor |
Also exports AddVariablesDropdown and BookingFieldVariable.
Call sites
| File | Notes |
|---|---|
apps/web/modules/ee/workflows/components/canvas/node-config-sheet.tsx | Reminder body + standalone subject variables |
apps/web/modules/event-types/components/tabs/privacy/event-privacy-tab.tsx | Private note + booking-field variables |
packages/features/eventtypes/components/tabs/setup/EventSetupTab.tsx | Event description |
packages/features/eventtypes/components/tabs/advanced/FormBuilder.tsx | Form-builder field description |
apps/web/modules/settings/my-account/components/profile-form-card.tsx | Profile bio |
Example URLs
Not exhaustive — a few places you can open locally (e.g. http://localhost:3000) to see this in product.
| Path | Notes |
|---|---|
/settings/my-account/profile | Profile bio Editor |
/event-types/<eventTypeId>?tabName=setup | Event description Editor |
/workflows/<workflowId> | Workflow reminder body / variables |