Skip to main content
RapidDev - Software Development Agency
outsystems-tutorial

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

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.

What you'll learn

  • How to add a JavaScript node to a Client Action flow in Service Studio
  • How to pass data in and out of JavaScript nodes using input and output parameters
  • How to call OutSystems Client Actions from JavaScript using the $actions API
  • How to load external JavaScript libraries using Script files
  • The decision framework for when JavaScript is appropriate versus OutSystems-native solutions
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate11 min read30-40 minOutSystems 11 and ODCMarch 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

typescript
1// JavaScript node example: Format currency
2// Inputs: Amount (Decimal), CurrencyCode (Text)
3// Outputs: FormattedAmount (Text)
4
5var formatter = new Intl.NumberFormat('en-US', {
6 style: 'currency',
7 currency: $parameters.CurrencyCode || 'USD',
8 minimumFractionDigits: 2
9});
10
11$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:**

typescript
1// In a JavaScript node (no inputs/outputs needed for this example)
2
3// Show a success notification using OutSystems public API
4$public.FeedbackMessage.showFeedbackMessage(
5 "File uploaded successfully!",
6 1, // 1 = Success (green)
7 true, // encode message (prevent XSS)
8 "" // no extra CSS classes
9);
10
11// Call a Client Action to refresh data after an external event
12$actions.RefreshTaskList();
13
14// Navigate programmatically
15$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:**

typescript
1// Prerequisites:
2// 1. Script 'QRCodeJS' added pointing to qrcode CDN
3// 2. Script set as Required on this screen
4// 3. A Container widget on screen with Name 'QRCodeContainer'
5// (add a widget ID expression: "QRCodeContainer")
6
7// JavaScript node:
8// Inputs: QRCodeText (Text), ContainerWidgetId (Text)
9// No outputs needed
10
11var containerElement = document.getElementById($parameters.ContainerWidgetId);
12if (containerElement) {
13 // Clear previous QR code if any
14 containerElement.innerHTML = '';
15
16 // Generate new QR code
17 QRCode.toCanvas(containerElement, $parameters.QRCodeText, {
18 width: 200,
19 margin: 2
20 }, function(error) {
21 if (error) {
22 $public.FeedbackMessage.showFeedbackMessage(
23 "QR code generation failed: " + error.message,
24 3, true, ""
25 );
26 }
27 });
28}

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 working example

javascript_node_patterns.js
1// ============================================================
2// OutSystems JavaScript Node — Common Patterns
3// All code runs inside JavaScript nodes in Client Actions
4// ============================================================
5
6// Pattern 1: Read clipboard content
7// Outputs: ClipboardText (Text)
8nnavigator.clipboard.readText().then(function(text) {
9 $parameters.ClipboardText = text;
10}).catch(function(err) {
11 $parameters.ClipboardText = '';
12 $public.FeedbackMessage.showFeedbackMessage(
13 'Clipboard access denied', 2, true, ''
14 );
15});
16
17// Pattern 2: Get user geolocation
18// Outputs: Latitude (Text), Longitude (Text), ErrorMessage (Text)
19if (navigator.geolocation) {
20 navigator.geolocation.getCurrentPosition(
21 function(position) {
22 $parameters.Latitude = position.coords.latitude.toString();
23 $parameters.Longitude = position.coords.longitude.toString();
24 $parameters.ErrorMessage = '';
25 },
26 function(error) {
27 $parameters.Latitude = '';
28 $parameters.Longitude = '';
29 $parameters.ErrorMessage = error.message;
30 }
31 );
32} else {
33 $parameters.ErrorMessage = 'Geolocation not supported';
34}
35
36// Pattern 3: Scroll to element by ID
37// Inputs: ElementId (Text)
38var element = document.getElementById($parameters.ElementId);
39if (element) {
40 element.scrollIntoView({ behavior: 'smooth', block: 'start' });
41}
42
43// Pattern 4: Copy text to clipboard
44// Inputs: TextToCopy (Text)
45navigator.clipboard.writeText($parameters.TextToCopy).then(function() {
46 $public.FeedbackMessage.showFeedbackMessage(
47 'Copied to clipboard!', 1, true, ''
48 );
49}).catch(function() {
50 // Fallback for older browsers
51 var textArea = document.createElement('textarea');
52 textArea.value = $parameters.TextToCopy;
53 document.body.appendChild(textArea);
54 textArea.select();
55 document.execCommand('copy');
56 document.body.removeChild(textArea);
57});
58
59// Pattern 5: Detect current device type
60// Outputs: DeviceType (Text) — 'phone', 'tablet', or 'desktop'
61var width = window.innerWidth;
62if (width < 768) {
63 $parameters.DeviceType = 'phone';
64} else if (width < 1024) {
65 $parameters.DeviceType = 'tablet';
66} else {
67 $parameters.DeviceType = 'desktop';
68}

Common mistakes

Why it's a problem: Trying to place a JavaScript node in a Server Action

How to avoid: 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.

Why it's a problem: Attempting to read an OutSystems screen variable directly in JavaScript (e.g., referencing a variable by name without $parameters)

How to avoid: 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.

Why it's a problem: Using a JavaScript node to navigate between screens (history.pushState or location.href) instead of the OutSystems Navigate action

How to avoid: 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.

Why it's a problem: Adding a large JavaScript library as a Required Script on every screen when it is only used on one screen

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

In my OutSystems Reactive Web app, I need a Client Action that (1) reads the user's current geolocation from the browser, (2) passes the latitude and longitude to an OutSystems Server Action that saves the coordinates to a database entity, and then (3) shows a success feedback message. Show me the complete action flow including the JavaScript node, its input/output parameters, and the Server Action call. Use OutSystems action flow arrow notation.

OutSystems Prompt

I want to integrate a QR code generator in my OutSystems app. I am using the qrcode.js library from CDN. In Service Studio, walk me through: (1) adding the script as a Required Script on my screen, (2) adding a JavaScript node to my Client Action, (3) setting up input parameters for the QR code text, and (4) writing the JS code to render the QR code into a Container widget on screen.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.