# JavaScript in OutSystems: When to Use It and How to Do It Right

- Tool: OutSystems
- Difficulty: Intermediate
- Time required: 30-40 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

## TL;DR

Add JavaScript to OutSystems Reactive apps by dragging a JavaScript node into a Client Action flow (Logic tab or right-click screen → Client Actions). Each JavaScript node has its own scope — exchange data with OutSystems via input and output parameters. Use $actions.<ActionName>() to call Client Actions from JS. Avoid JavaScript when an OutSystems expression or built-in widget property can do the same thing.

## JavaScript as an Escape Hatch

OutSystems Reactive Web apps compile to JavaScript/TypeScript that runs in the browser. For most tasks — data manipulation, navigation, API calls, form validation — OutSystems-native constructs (expressions, Client Actions, Server Actions) are the right tool. JavaScript nodes are the escape hatch for cases where native constructs cannot reach: direct DOM manipulation, browser APIs, third-party library integration, and performance-critical in-browser operations. This tutorial covers the mechanics of JavaScript integration and the decision criteria for using it responsibly.

## Before you start

- A Reactive Web application open in Service Studio
- A Client Action to work with (right-click any screen → Add Client Action)
- JavaScript knowledge: variables, functions, DOM API, browser APIs
- Understanding of OutSystems Client vs Server Action distinction

## Step-by-step guide

### 1. Add a JavaScript Node to a Client Action

JavaScript nodes can only be placed in **Client Actions** — they run in the browser and cannot be used in Server Actions.

To add a JavaScript node:
1. Open a Client Action in the Action Flow Editor (right-click a screen → 'Add Client Action', or find one in Logic tab → Client Actions)
2. In the Action Flow Editor, look at the Toolbox on the left
3. Scroll down to find the **JavaScript** element (or search 'JavaScript' in the Toolbox search)
4. Drag the JavaScript element from the Toolbox into the action flow between Start and End

Double-click the JavaScript node to open its editor. You see:
- A code editor for your JavaScript (top area)
- An **Inputs** section (bottom-left) — data you pass INTO the JavaScript node from OutSystems
- An **Outputs** section (bottom-right) — data the JavaScript node returns to OutSystems

The JavaScript node runs in an isolated scope — it cannot directly access OutSystems screen variables or widgets. All data exchange must go through the Input/Output parameter mechanism.

**Expected result:** A JavaScript node appears in your Client Action flow between Start and End. Double-clicking it opens the JavaScript editor with empty code, Inputs, and Outputs sections.

### 2. Exchange Data with Input and Output Parameters

JavaScript nodes communicate with OutSystems through parameters — not through direct variable access.

**Adding Input Parameters (OutSystems → JavaScript):**
1. In the JavaScript node editor, click the **+** icon in the Inputs section
2. Set Name (e.g., `UserName`) and Data Type (Text, Integer, Boolean, etc.)
3. Back in the Action Flow Editor, select the JavaScript node and look at its properties
4. The input parameters appear as fields — set each to an OutSystems expression or variable value

**Adding Output Parameters (JavaScript → OutSystems):**
1. Click the **+** icon in the Outputs section
2. Set Name (e.g., `FormattedText`) and Data Type
3. In your JavaScript code, assign the result to the parameter name directly:
   `$parameters.FormattedText = UserName.toUpperCase();`

**Accessing parameters in JavaScript code:**
- Inputs: `$parameters.InputParamName`
- Outputs: `$parameters.OutputParamName = value;`

After the JavaScript node completes, the Output parameter values are available as variables in the action flow — assign them to screen variables or pass them to other nodes.

```
// JavaScript node example: Format currency
// Inputs: Amount (Decimal), CurrencyCode (Text)
// Outputs: FormattedAmount (Text)

var formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: $parameters.CurrencyCode || 'USD',
  minimumFractionDigits: 2
});

$parameters.FormattedAmount = formatter.format($parameters.Amount);
```

**Expected result:** The JavaScript node receives the Amount and CurrencyCode values from OutSystems, formats them, and returns the result in FormattedAmount — which is then available in the action flow as a variable.

### 3. Call OutSystems Client Actions from JavaScript

OutSystems exposes a `$actions` API that lets JavaScript code trigger Client Actions defined in the module.

**Calling a Client Action from JavaScript:**
`$actions.MyClientActionName(param1, param2);`

**With a callback when the action completes:**
`$actions.MyClientActionName(param1).then(function(result) { ... });`

**OutSystems public APIs available in JavaScript:**
- `$public.FeedbackMessage.showFeedbackMessage(message, type, encodeMessage, extraCssClasses)` — show a toast notification
  - Types: 0=Info, 1=Success, 2=Warning, 3=Error
- `$public.Navigation.navigate(url, method)` — navigate to a URL
- `$public.Security.getCSRFToken()` — get the CSRF token for custom API calls
- `$public.Logger.logError(moduleName, errorMessage, errorDescription)` — write to OutSystems error log
- `$public.Validation.validateWidget(widgetId)` — trigger validation on a specific widget

**Practical example — triggering a success notification from JS:**

```
// In a JavaScript node (no inputs/outputs needed for this example)

// Show a success notification using OutSystems public API
$public.FeedbackMessage.showFeedbackMessage(
  "File uploaded successfully!",
  1,     // 1 = Success (green)
  true,  // encode message (prevent XSS)
  ""     // no extra CSS classes
);

// Call a Client Action to refresh data after an external event
$actions.RefreshTaskList();

// Navigate programmatically
$public.Navigation.navigate("/TaskManager/Tasks", "navigate");
```

**Expected result:** Calling $public.FeedbackMessage.showFeedbackMessage() from a JavaScript node displays a toast notification in the running app. Calling $actions.RefreshTaskList() triggers your Client Action.

### 4. Load and Use an External JavaScript Library

For third-party libraries (chart renderers, PDF viewers, QR code generators, etc.), load them as Script resources.

**Method A — External URL (CDN):**
1. Interface tab → Scripts → right-click → **Add Script**
2. In the dialog, set Name (e.g., 'QRCodeJS')
3. Set URL to the CDN URL: `https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js`
4. Set Required Script: on the Screen(s) that use it — select the screen in the Interface tab → Properties → Required Scripts → add the script

**Method B — Local file:**
1. Interface tab → Scripts → right-click → **Add Script**
2. In the dialog, click 'Import File' → select the .js file from your filesystem
3. Set Required Script on the screen

**Using the library in a JavaScript node:**
After adding the script, the library is available in the global scope of your JavaScript nodes on that screen.

**Example — generating a QR code:**

```
// Prerequisites:
// 1. Script 'QRCodeJS' added pointing to qrcode CDN
// 2. Script set as Required on this screen
// 3. A Container widget on screen with Name 'QRCodeContainer'
//    (add a widget ID expression: "QRCodeContainer")

// JavaScript node:
// Inputs: QRCodeText (Text), ContainerWidgetId (Text)
// No outputs needed

var containerElement = document.getElementById($parameters.ContainerWidgetId);
if (containerElement) {
  // Clear previous QR code if any
  containerElement.innerHTML = '';

  // Generate new QR code
  QRCode.toCanvas(containerElement, $parameters.QRCodeText, {
    width: 200,
    margin: 2
  }, function(error) {
    if (error) {
      $public.FeedbackMessage.showFeedbackMessage(
        "QR code generation failed: " + error.message,
        3, true, ""
      );
    }
  });
}
```

**Expected result:** The QRCode.js library is loaded on the screen. The JavaScript node can call QRCode.toCanvas() without any import statements — the library is in the global scope.

### 5. Apply the JavaScript Decision Framework

Before adding a JavaScript node, check whether an OutSystems-native solution exists. JavaScript adds maintenance overhead and is harder to debug than visual action flows.

**Use JavaScript ONLY when:**
- Accessing browser APIs not exposed by OutSystems (geolocation, clipboard, Web Audio, WebSockets, ResizeObserver)
- Manipulating the DOM in ways not possible through widget properties
- Integrating a third-party library that requires direct DOM access (charting, rich text editors, map renderers)
- Performance-critical in-browser calculations (e.g., processing a large local array without server round-trip)
- Calling $public APIs (FeedbackMessage, Navigation) for custom trigger patterns

**Do NOT use JavaScript when OutSystems can do it:**
- String manipulation → use OutSystems expressions: `ToUpper()`, `Replace()`, `Substr()`
- Date formatting → `FormatDate(CurrDate(), "yyyy-MM-dd")`
- Showing/hiding elements → If widget or Visible property
- Form validation → set widget's Valid and ValidationMessage properties in a Client Action
- Navigation → Navigate system action in Client Action
- Showing notifications → Feedback Message widget or SetFeedbackMessage action
- Ajax data fetch → Server Action call from Client Action

**Anti-pattern to avoid:** Using JavaScript nodes to call OutSystems REST APIs directly (bypassing OutSystems' security headers, CSRF protection, and error handling). Always make data calls through Server Actions.

**Expected result:** You have a clear mental model of when JavaScript is the right tool and when OutSystems-native constructs are better. Your codebase uses JavaScript only where necessary.

## Complete code example

File: `javascript_node_patterns.js`

```javascript
// ============================================================
// OutSystems JavaScript Node — Common Patterns
// All code runs inside JavaScript nodes in Client Actions
// ============================================================

// Pattern 1: Read clipboard content
// Outputs: ClipboardText (Text)
nnavigator.clipboard.readText().then(function(text) {
  $parameters.ClipboardText = text;
}).catch(function(err) {
  $parameters.ClipboardText = '';
  $public.FeedbackMessage.showFeedbackMessage(
    'Clipboard access denied', 2, true, ''
  );
});

// Pattern 2: Get user geolocation
// Outputs: Latitude (Text), Longitude (Text), ErrorMessage (Text)
if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(
    function(position) {
      $parameters.Latitude = position.coords.latitude.toString();
      $parameters.Longitude = position.coords.longitude.toString();
      $parameters.ErrorMessage = '';
    },
    function(error) {
      $parameters.Latitude = '';
      $parameters.Longitude = '';
      $parameters.ErrorMessage = error.message;
    }
  );
} else {
  $parameters.ErrorMessage = 'Geolocation not supported';
}

// Pattern 3: Scroll to element by ID
// Inputs: ElementId (Text)
var element = document.getElementById($parameters.ElementId);
if (element) {
  element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

// Pattern 4: Copy text to clipboard
// Inputs: TextToCopy (Text)
navigator.clipboard.writeText($parameters.TextToCopy).then(function() {
  $public.FeedbackMessage.showFeedbackMessage(
    'Copied to clipboard!', 1, true, ''
  );
}).catch(function() {
  // Fallback for older browsers
  var textArea = document.createElement('textarea');
  textArea.value = $parameters.TextToCopy;
  document.body.appendChild(textArea);
  textArea.select();
  document.execCommand('copy');
  document.body.removeChild(textArea);
});

// Pattern 5: Detect current device type
// Outputs: DeviceType (Text) — 'phone', 'tablet', or 'desktop'
var width = window.innerWidth;
if (width < 768) {
  $parameters.DeviceType = 'phone';
} else if (width < 1024) {
  $parameters.DeviceType = 'tablet';
} else {
  $parameters.DeviceType = 'desktop';
}
```

## Common mistakes

- **Trying to place a JavaScript node in a Server Action** — undefined Fix: JavaScript nodes run in the browser and are only available in Client Actions. Server Actions run on the server (.NET) and cannot execute JavaScript. Move your JavaScript logic to a Client Action and call that Client Action from your flow.
- **Attempting to read an OutSystems screen variable directly in JavaScript (e.g., referencing a variable by name without $parameters)** — undefined Fix: JavaScript nodes are isolated from the OutSystems runtime scope. You cannot reference screen variables, local variables, or widget values directly. Always pass values as Input Parameters to the JavaScript node and receive results through Output Parameters.
- **Using a JavaScript node to navigate between screens (history.pushState or location.href) instead of the OutSystems Navigate action** — undefined Fix: Navigating via raw JavaScript bypasses OutSystems' navigation system, breaking back button behavior, URL parameter passing, and screen lifecycle events. Use the Navigate system action in a Client Action flow, or $public.Navigation.navigate() for programmatic cases.
- **Adding a large JavaScript library as a Required Script on every screen when it is only used on one screen** — undefined Fix: Set Required Scripts at the most specific level possible. If a library is only used on one screen, add it only to that screen's Required Scripts property. Loading unnecessary scripts on every screen slows initial app load time.

## Best practices

- Keep JavaScript nodes small and focused — one node, one responsibility. Complex multi-step JS is hard to debug in the Service Studio editor.
- Always use $parameters for data exchange — never try to access OutSystems screen variables directly from JavaScript code
- Use $public.FeedbackMessage rather than alert() for user notifications — alert() blocks execution and looks unprofessional
- Load external libraries via Script resources, not via document.createElement('script') inside a JavaScript node — dynamic script injection can fail due to content security policies
- Add error handling in JavaScript nodes — uncaught JS errors can crash the Client Action flow silently; wrap async operations in try/catch or .catch()
- Test JavaScript nodes with the Service Studio Debugger — set a breakpoint before the JavaScript node, then use browser DevTools console for the JavaScript execution itself
- Document any JavaScript nodes with a comment explaining why native OutSystems constructs were insufficient — this prevents future developers from rewriting the JS unnecessarily

## Frequently asked questions

### Can I use modern JavaScript (ES6+, async/await, arrow functions) in OutSystems JavaScript nodes?

Yes. OutSystems Reactive Web apps target modern browsers and the JavaScript runtime supports ES6+ features including arrow functions, template literals, destructuring, Promises, and async/await. Just make sure your target browser support requirement aligns with the ES features you use. Avoid features below ES2020 compatibility if your users may be on older browsers.

### How do I debug JavaScript errors in OutSystems?

Use browser DevTools (F12) in the running app. JavaScript errors from OutSystems JavaScript nodes appear in the Console tab. You can add console.log() calls in your JavaScript node code during development — they appear in the Console. For stepping through JavaScript execution, use the Sources tab in DevTools and set breakpoints on your code. The Service Studio Debugger can help you verify what values are being passed to the JavaScript node's input parameters.

### Can JavaScript in OutSystems access the user's session token or authentication headers?

Yes, with caution. $public.Security.getCSRFToken() returns the CSRF token that OutSystems uses for its own API calls. For custom API calls from JavaScript, you should still go through OutSystems Server Actions rather than making direct API calls from the client — this avoids exposing API keys in client-side code and ensures OutSystems security headers are applied correctly.

### What is the difference between a JavaScript node and a Script resource in OutSystems?

A JavaScript node (in a Client Action) is inline JavaScript that runs when the action is executed — it is code that runs in response to an event (button click, screen load, etc.). A Script resource (Interface tab → Scripts) is a JavaScript file (or CDN URL) that is loaded on a screen as a library, making the library's global objects available to all JavaScript nodes on that screen. Use Script resources for external libraries; use JavaScript nodes for the logic that calls them.

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-javascript-integration
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-javascript-integration
