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
Add a JavaScript Node to a Client Action
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.
Exchange Data with Input and Output Parameters
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.
1// JavaScript node example: Format currency2// Inputs: Amount (Decimal), CurrencyCode (Text)3// Outputs: FormattedAmount (Text)45var formatter = new Intl.NumberFormat('en-US', {6 style: 'currency',7 currency: $parameters.CurrencyCode || 'USD',8 minimumFractionDigits: 29});1011$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.
Call OutSystems Client Actions from JavaScript
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:**
1// In a JavaScript node (no inputs/outputs needed for this example)23// Show a success notification using OutSystems public API4$public.FeedbackMessage.showFeedbackMessage(5 "File uploaded successfully!",6 1, // 1 = Success (green)7 true, // encode message (prevent XSS)8 "" // no extra CSS classes9);1011// Call a Client Action to refresh data after an external event12$actions.RefreshTaskList();1314// Navigate programmatically15$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.
Load and Use an External JavaScript Library
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:**
1// Prerequisites:2// 1. Script 'QRCodeJS' added pointing to qrcode CDN3// 2. Script set as Required on this screen4// 3. A Container widget on screen with Name 'QRCodeContainer'5// (add a widget ID expression: "QRCodeContainer")67// JavaScript node:8// Inputs: QRCodeText (Text), ContainerWidgetId (Text)9// No outputs needed1011var containerElement = document.getElementById($parameters.ContainerWidgetId);12if (containerElement) {13 // Clear previous QR code if any14 containerElement.innerHTML = '';1516 // Generate new QR code17 QRCode.toCanvas(containerElement, $parameters.QRCodeText, {18 width: 200,19 margin: 220 }, 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.
Apply the JavaScript Decision Framework
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
1// ============================================================2// OutSystems JavaScript Node — Common Patterns3// All code runs inside JavaScript nodes in Client Actions4// ============================================================56// Pattern 1: Read clipboard content7// 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});1617// Pattern 2: Get user geolocation18// 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}3536// Pattern 3: Scroll to element by ID37// Inputs: ElementId (Text)38var element = document.getElementById($parameters.ElementId);39if (element) {40 element.scrollIntoView({ behavior: 'smooth', block: 'start' });41}4243// Pattern 4: Copy text to clipboard44// 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 browsers51 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});5859// Pattern 5: Detect current device type60// 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation