Code node JavaScript API
Look up flow Code node RM methods, return values, visitor data, and asynchronous email-platform writes.
A flow Code node runs your JavaScript on the visitor's page to read data, change it, or display a result. Its local RM object is separate from the browser's window.RM command queue and rm:* events. See Browser JavaScript commands and events for those interfaces.
Code changes save to the project's draft. They affect normal visitors after publication. The methods use the installed project's configuration and available integration, not a REST API token. Browser code is visible to visitors. Keep credentials and authorization decisions on your server.
Code editor controls
The flow builder lists Code under Actions. The node editor is titled Execute or Embed Code.
| Control | Effect |
|---|---|
| Friendly Name | Labels the node in the flow. It does not change the code's behavior. |
| JavaScript Code | Contains the browser JavaScript or HTML insertion content described below. |
| Selector | CSS selector for the HTML insertion path. The field appears when the code contains HTML-like markup or document.querySelector. Merely showing this field does not mean the runtime will use the HTML path. |
| Add Data | Opens the flow data picker when data is available. Selecting a token inserts its translated expression at the cursor, or appends it if the editor cursor is unavailable. |
| AI Generate | Opens Generate JavaScript Code with AI. Enter what the code should do or how existing code should change, then choose Generate Code. |
Display Mode Detected is an editor hint based on static analysis. The actual returned value determines whether the runtime displays content.
Execution and display results
The runtime renders Liquid in the code first, then executes the result synchronously with new Function('RM', code). Top-level await is not supported. Returned Promises are not awaited.
| Result | Flow behavior |
|---|---|
| Nonempty string | Displays the string as widget content instead of continuing past the node. Strings can contain HTML. |
HTMLElement | Displays the element as widget content. |
{ display: true, content } | Displays a truthy content value. Use a string or an element. |
undefined or null | Continues the flow without display content. |
| Number, boolean, Promise, empty string, or other non-displayable value | Logs a warning and continues. |
| Synchronous exception | Logs the error and failed code, records a debugger error, and continues. Earlier side effects are not rolled back. |
A separate HTML-insertion path applies when the source contains HTML-like markup but no return followed by whitespace. It parses the source as HTML and appends it to Selector, or to body when no selector is set. Top-level script elements are recreated to execute them. A missing target records an error. This detection is text-based, so an HTML-looking JavaScript string can select the insertion path unexpectedly.
For a JavaScript calculation that displays HTML, an explicit return avoids that ambiguity. Illustrative example, not executed:
Expected result: the widget displays a paragraph containing the current score. Using textContent keeps visitor data out of raw HTML.
Variables and scores
| Method | Arguments | Return and effect |
|---|---|---|
RM.getVariable(name) | name: string | Stored JSON-compatible value, or null when missing or expired. Reading an expired value removes it. |
RM.setVariable(name, value, options = {}) | String name, JSON-compatible value, optional expiry object below | undefined. Persists the value in browser storage and refreshes variable context. |
RM.getScore(scoreName) | String score name | Current score, or 0 when absent. |
RM.adjustScore(scoreName, amount) | String name and numeric adjustment | undefined. Adds an adjustment and refreshes score-dependent state. Negative amounts subtract. |
RM.setScore(scoreName, value) | String name and numeric value | undefined. Replaces existing adjustments for that score with the given value. |
setVariable accepts these nested options:
| Option | Default | Values |
|---|---|---|
expiresType | null | Use 'duration' for a timed value. Other values do not add an expiry. |
expiresDuration | null | Duration amount. Supply a positive number. |
expiresUnit | null | 'minutes', 'hours', 'days', or 'weeks'. |
An expiry requires all three duration settings and a recognized unit. There is no timer callback at expiry. Reads and context refreshes omit expired values. Each write replaces the previous value and expiry. Without expiry options, the value has no scheduled expiry, but clearing browser data can remove it.
Unlike a Set Data node, RM.setVariable(name, '') stores an empty string. It does not delete the variable. Values must survive JSON serialization. Circular objects and unsupported JSON values are not valid storage inputs.
Scores are calculated runtime values, not durable variables. For a value that must survive a page load, use a variable. See Flow data and Liquid reference for the distinct score, variable, and input paths.
Illustrative calls and values, not executed:
Question inputs
| Method | Arguments | Return and effect |
|---|---|---|
RM.getInput(questionIdOrText) | Exact question ID, or case-insensitive trimmed question text | Stored input value. For a segment-choice question without a direct input value, returns the selected option label or comma-separated labels. Returns null for no answer or an unknown question. |
RM.setInput(questionIdOrText, value) | Same lookup plus a value | undefined. Records and persists the input. An unknown question logs an error and changes nothing. |
Question text matching compares stored text, including any markup. IDs avoid ambiguous or edited labels. setInput stores an answer. It is not a form submission or a guarantee that an email-platform field has been written.
Segment answers
| Method | Arguments | Return and effect |
|---|---|---|
RM.getSegments() | None | Array of active segment ID strings from current segmentation. |
RM.addSegment(segment) | Segment ID beginning seg_, or segment name | undefined. Records a hard answer and clears other recorded answers in the same group. |
RM.addSegment(group, segment) | Group ID or name, then segment ID or name | Same effect, with lookup constrained to that group. |
RM.removeSegment(segment) | Segment ID or name | undefined. Removes that recorded answer. |
RM.removeSegment(group, segment) | Group ID or name, then segment ID or name | Same removal with group lookup. |
Names are compared case-insensitively after trimming. A single name that matches several groups logs a warning and uses the first match. Failed lookups log an error and do nothing. Use IDs for stable references.
addSegment replaces sibling answers even in a multi-select group. These methods change recorded answers, not the group's signal rules. Removing an answer does not guarantee that the visitor stops matching a signal-based segment.
Unlike window.RM.push(['addSegment', id]), Code-node segment methods do not emit the public browser command's segmentation refresh events. They also do not guarantee an immediate provider sync.
Email-platform fields
Field methods require an active integration. Field lookup checks custom and native fields by ID first, then by case-insensitive trimmed templating token. Pass an ID as a string or a token returned by RM.listCustomFields().
| Method | Arguments | Return and failure behavior |
|---|---|---|
RM.getCustomField(fieldIdOrToken, parseJson = false) | Field ID or token, optional boolean | Current loaded field value, or null when the integration, field, or value is missing. With parseJson: true, returns parsed JSON when parsing succeeds. On parse failure, warns and returns the original value. |
RM.setCustomField(fieldIdOrToken, value) | Existing field ID or token and provider-compatible value | Promise resolving to undefined when a field resolves. Missing integration or field logs an error and returns undefined. Provider failure is caught and logged, so a resolved Promise is not a success receipt. |
setCustomField routes native fields through their native-field identifier. It can queue a field write while identity is missing or being resolved. Queueing is not confirmation of delivery. It does not create a field, and a read immediately after a write is not a guaranteed refreshed provider value.
Email-platform tags
| Method | Arguments | Return and effect |
|---|---|---|
RM.addTag(tagId) | Exact provider tag ID, preserving its type | undefined. Starts an asynchronous addition. |
RM.removeTag(tagId) | Exact provider tag ID | undefined. Starts an asynchronous removal if the integration supports it. |
RM.hasTag(tagId) | Exact provider tag ID | Boolean based on currently loaded tag IDs. |
Tag-name lookup is not supported. Both write methods require an identified visitor with an email or subscriber ID. Missing integration or identity logs an error and does not queue a tag. This differs from Set Data tag actions, which can queue anonymous visitors' changes.
Provider errors are logged asynchronously. Tag methods do not return a Promise or delivery receipt, and hasTag is not a post-write acknowledgement. Without integration or identity, hasTag returns false and logs a message.
Available questions, segments, fields, and context
| Method | Return shape |
|---|---|
RM.listQuestions() | Array of { id, text, options: [{ id, label, segmentId }] }. Omits questions with empty text or only empty HTML. |
RM.listSegments() | Object keyed by group name, with arrays of { id, name, isActive }. Duplicate group names can overwrite a key. |
RM.listCustomFields() | Array of { id, token, value } for loaded custom and native fields. Returns [] and warns without an integration. |
RM.getVisitorContext() | Shallow-frozen object containing serializable visitor-context entries. Functions and source objects are skipped. Unserializable entries become '[Not serializable]'. |
getVisitorContext() does not deep-freeze nested values or fetch missing profile data. Its integration namespace depends on the connected provider. It can include personal information and should not be sent to unrelated analytics systems or public logs.
Illustrative discovery result, not executed:
Add Data and AI Generate
Add Data translates simple score, variable, and input tokens into RM.getScore, RM.getVariable, and RM.getInput calls. Other tokens can become comments rather than executable expressions. In particular, an integration-data comment does not mean the field methods above are unavailable.
AI Generate uses the existing code and the flow's questions, scores, and variables as context. A successful result replaces the node's code in the draft. Generation is not execution, a security review, or publication. Review generated code for display returns, provider writes, and repeated side effects before publishing.
For a provider write, verify the result in the connected integration. A finished Code node is not a delivery confirmation.