Scripting environment reference
Learn about the custom script's engine and language level, supported and unsupported JavaScript, polling recommendations, and more.
A custom script runs inside Orange Logic, not in a browser or Node.js. Orange Logic creates a new engine for each run, injects a set of global objects, and disposes of the engine when the run ends. Script variables are not saved to the next run.
JavaScript engine and language level
Custom scripts run on Microsoft ClearScript 7.5.0, which hosts V8 13.3, the engine of Chrome 133.
The language level is ES2024 in full, plus the ES2025 features that shipped in Chrome 133: Set methods, iterator helpers, Promise.try, RegExp modifiers, and duplicate named capture groups. Anything newer than Chrome 133 is unavailable, including Float16Array and RegExp.escape.
Windows and Linux hosts behave the same.
Supported and unsupported JavaScript
The following JavaScript features are supported:
-
asyncfunctions andawait -
Promises
-
Classes with private and static fields
-
Optional chaining
-
Nullish coalescing
-
Spread and destructuring
-
BigInt -
Array.prototype.at -
String.replaceAll -
Object.hasOwn -
JSON -
RegExp -
Date -
Math
-
Intl -
typed arrays
-
Map -
Set -
WeakMap -
WeakRef -
Generators
-
Symbols
-
Proxy -
Reflect -
template literals
-
Getters and setters
Orange Logic evaluates each script as a classic script rather than a module, so the following apply:
importandexportare unavailable, and so is dynamicimport(). Keep everything the code needs in the one script.- Top-level
awaitis unavailable. To useawait, wrap the code in an async function that runs immediately, and make its promise the script's last expression.
The following are absent from the runtime:
fetch. Use Execute HTTP request orXMLHttpRequestinstead.- The DOM, including
documentandwindow. TheNodeglobal provides the node type constants and nothing else. - Web Crypto, including
crypto.getRandomValuesandcrypto.randomUUID.Math.randomworks. To sign, use Sign JWT with RSA orCortex.ComputeHMACSHA256. TextEncoderandTextDecoder. UseEncoding.UTF8for byte work.- The file system,
process, andrequire. - A
consoleobject connected to the logs. UseLoginstead. For more information, go to Logs and troubleshooting.
A Node.js or browser library that depends on any of the above does not load. A small library with no dependencies can be pasted into the script.
Global objects and namespaces
Orange Logic injects the following globals into every script, alongside the standard JavaScript built-ins.
| Global | Explanation |
|---|---|
ScriptResult | The result the script returns to the action. |
Utils | Callback-style wrappers over the Cortex functions and XMLHttpRequest. Go to Utilities for more information. |
Log | Log.Verbose, Log.Info, Log.Warn, and Log.Error write to the Orange Logic server log. |
Cortex | The underlying Orange Logic functions that Utils wraps, including CallInternalAPI, ExecuteAssetUpload, ImportFromThirdParty, GetCache, StoreCache, SignJWTWithPrivateRSAKe, ComputeHMACSHA256, PublishMessageToAWSSNS, and ExportToS3. Use Utils wherever a wrapper exists. |
XMLTools | Parses, updates, and deletes XML. Go to Tools for more information. |
JsonConvert | Newtonsoft JSON serialization. Native JSON.parse and JSON.stringify are the normal choice. |
Convert, Encoding | .NET type conversion and text encoding, such as Encoding.UTF8.GetBytes(s) and Convert.ToBase64String(bytes). |
Thread | Thread.Sleep(ms) pauses the script. |
Locker | A distributed lock. Use it through Utils.ExecuteWithLock. |
Action | Passes a JavaScript function to a .NET method, as new Action(fn). Utils.ExecuteWithLock uses it. |
Base64, btoa, atob | Base64 encoding and decoding. |
setTimeout, setInterval, clearTimeout, clearInterval | Timers. A timer callback runs only while Orange Logic is still waiting on the script. |
Promise | The native promise, wrapped so that Orange Logic waits for every pending promise before the run ends. |
XMLHttpRequest | Synchronous or asynchronous HTTP over .NET HttpClient. responseXML is not implemented, and upload is a stub. |
FormData, Blob | Multipart request bodies for XMLHttpRequest. |
URL, URLSearchParams | URL parsing and query string building. |
Placeholders are not globals. Orange Logic substitutes them into the script text before it compiles the script, so [Document.RecordID] becomes a literal value.
How a script runs
When an action triggers a script, Orange Logic completes the following steps:
- Substitutes the placeholders into the script text.
- Reads the directive block at the top of the script.
- Creates a new engine with the heap limits, the globals, and the polyfills, then evaluates the script.
- Waits for the script's last expression if it is a promise, then for every pending promise, then for every pending
XMLHttpRequest, then forScriptResult.Dataif it is a promise. - Serializes
ScriptResultand returns it to the action. - Disposes of the engine.
Timer callbacks run only during step 4, so a setTimeout that no promise awaits never runs. Orange Logic sets no limit on how many scripts run at the same time.
Return a result with ScriptResult
Every script reports back through ScriptResult:
Successdetermines whether the action succeeded. It defaults to true.MessageandDetailsappear in the failure notification.ScriptResult.Step(text)appends a line toDetails, which gives you a trace of what the script did.Datais returned to the action as JSON, and a downstream action reads it from there.Utils.SetScriptOutput(value)sets it as well.StatusCodeis returned as the HTTP status code on the response.
When Success is false, the action fails. The API answers with HTTP 500 and the text of Message and Details, the failure notification is sent, and a script set to retry is rescheduled.
Caution
Utils.ExecuteHttpRequestsetsScriptResult.StatusCodeto the status of the last call it made, and setsScriptResult.Successto false on any status outside the 2xx range. When your script treats a status such as 404 or 409 as a normal outcome, setScriptResult.Successback to true. Otherwise, Orange Logic reports the action as failed.
Wait for an external job to finish
Some external services accept a request, return a job ID, and finish the work afterward. To use the result in the same script, poll the service's status endpoint until the job finishes. Run that loop inside the script rather than through Asynchronous processing retries, because the wait between attempts grows to hours.
Keep the script synchronous and callback-based. Synchronous code runs entirely inside the script timeout, and it is easier to follow.
Pause between polls with Thread.Sleep. It yields the processor, the script timeout interrupts it as soon as it returns, and it does not hang. Do not wait by looping on Date.now(), because a busy loop occupies a processor core for the entire wait period.
You can also await a promise that resolves on a timer inside an async function whose promise is the script's last expression. Keep that form for a library that needs promise semantics. Always add your own elapsed-time budget because the script timeout does not fire while the script awaits a timer.
CautionIf the script timeout lands while a timer callback is running, the callback stops and the script never ends. Use
Thread.Sleepto pause unless a dependency requires promises.
Build the loop with the following limits:
- An interval: Wait 3 to 5 seconds between polls. A shorter interval adds load without returning the result sooner.
- An elapsed-time budget: Stop once the loop passes a set duration, and keep that duration below the action's Timeout (seconds).
- A backoff on 429: When the service returns 429 Too Many Requests, wait longer before the next poll, and set
ScriptResult.Successback to true, because the non-2xx status cleared it. - An explicit request timeout: Pass
Timeoutin milliseconds on each call rather than relying on the 20-minute default, so the budget plus one request timeout stays below the action's timeout.
Example
The following loop polls the status endpoint of a job that an earlier part of the script already submitted.
var POLL_INTERVAL_MS = 4000;
var POLL_BUDGET_MS = 150000; // keep this below the action's Timeout (seconds)
var start = Date.now();
var done = false;
while (!done) {
if (Date.now() - start > POLL_BUDGET_MS) {
ScriptResult.Success = false;
ScriptResult.Message = 'Polling timed out';
break;
}
Utils.ExecuteHttpRequest(
statusUrl,
{ Method: 'GET', Headers: headers, Timeout: 30000 },
// Success callback function
function (xhr) {
var status = JSON.parse(xhr.responseText);
done = (status.status === 'succeeded');
},
// Failure callback function
function (xhr) {
if (xhr.status === 429) {
Thread.Sleep(POLL_INTERVAL_MS * 2);
ScriptResult.Success = true; // 429 is expected here, so clear the failure
} else {
done = true;
}
}
);
if (!done) {
Thread.Sleep(POLL_INTERVAL_MS);
}
}A synchronous action has to finish while the user waits, so this pattern suits a job that completes in seconds. When an external job runs longer than that, split the work. Have the first action submit the job and store the job ID in metadata or in the cache, then handle completion with a webhook into a dynamic endpoint, or with a scheduled trigger that polls once per run.
Share values between runs
Each script runs in a new engine, so nothing a script holds in a variable survives the run. To reuse a value such as an access token, store it with Utils.GetOrAddCache. The cache is shared across the runs and the servers of your site. Set the expiry shorter than the token's own lifetime.
Prevent two runs from colliding
When more than one trigger can run the same integration for the same record, wrap the work in Utils.ExecuteWithLock(key, fn, options). It returns true when it acquired the lock and ran your function. The options are TimeOutSeconds, which defaults to 60, MaxAttempts, which defaults to 3, MinWaitSeconds, which defaults to 1, and MaxWaitSeconds, which defaults to 5.
Script directives
Orange Logic reads a block of comment lines at the top of the script, one directive per line. Parsing stops at the first line that is not a comment.
// MaxHeapSizeMB raises the heap budget for that script. The author needs the Can use the MaxHeapSizeMB directive in Run script action Security Function.
Memory limits
Each script runs with a soft heap budget of 4 MB, and the hard wall is twice that budget. The heap doubles as it expands. When a script passes the budget, Orange Logic raises a script error that the script can catch.
To raise the budget for one script, add // MaxHeapSizeMB at the top of it. An Orange Logic specialist can set a higher budget for your Orange Logic environment.
Defaults
The following platform defaults apply when you leave a setting empty.
| Setting | Default | Applies when |
|---|---|---|
| Script timeout | 7,200 seconds, which is two hours | Timeout (seconds) is empty or 0 |
| HTTP request timeout | 20 minutes | A request made with Execute HTTP request or XMLHttpRequest sets no Timeout of its own. An unlimited timeout is not supported. |
| Maximum retries | 10 | Max Retries is empty on an Asynchronous processing script |
| Wait before a retry | 1 minute minimum, 24 hours maximum | An asynchronous script retries a failed run |
| Script heap budget | 4 MB soft, 8 MB hard | Every script, unless // MaxHeapSizeMB raises it |
Updated 6 days ago

