# Code node JavaScript API

Look up flow Code node RM methods, return values, visitor data, and asynchronous email-platform writes.

Available on the Grow, Scale, and Pro plans.

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](https://rightmessage.com/help/javascript-hooks) for those interfaces.

Code changes save to the project's draft. They affect normal visitors after [publication](https://rightmessage.com/help/publishing-your-changes). 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 node editor with a WorkForge score result, JavaScript Code, Add Data, and AI Generate](https://rightmessage.com/help/screens/developers-code-node.light.webp)

## 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:

```javascript
const result = document.createElement('p');
result.textContent = `Your WorkForge fit score is ${RM.getScore('fit')}.`;
return result;
```

Expected result: the widget displays a paragraph containing the current score. Using `textContent` keeps visitor data out of raw HTML.

> **Warning**
>
> Customer-authored Code nodes still execute in the on-site debugger. RightMessage cannot prevent network requests, third-party tracking, or other side effects in your own JavaScript. A preview is not a sandbox for arbitrary code.

## 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](https://rightmessage.com/help/flow-data-liquid-reference) for the distinct `score`, `variable`, and `input` paths.

Illustrative calls and values, not executed:

```javascript
RM.setVariable('workforge_tier', 'team', {
	expiresType: 'duration',
	expiresDuration: 7,
	expiresUnit: 'days'
});
RM.setScore('fit', 10);
RM.adjustScore('fit', 5);
console.log(RM.getVariable('workforge_tier')); // "team"
console.log(RM.getScore('fit')); // 15
```

## 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](https://rightmessage.com/help/flow-data-liquid-reference#tags-and-identification), 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:

```json
{
	"Lifecycle": [
		{ "id": "seg_workforge_lead", "name": "Lead", "isActive": true },
		{ "id": "seg_workforge_customer", "name": "Customer", "isActive": false }
	]
}
```

## 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](https://rightmessage.com/help/verify-an-integration-change). A finished Code node is not a delivery confirmation.
