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:

  • async functions and await

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

  • import and export are unavailable, and so is dynamic import(). Keep everything the code needs in the one script.
  • Top-level await is unavailable. To use await, 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 or XMLHttpRequest instead.
  • The DOM, including document and window. The Node global provides the node type constants and nothing else.
  • Web Crypto, including crypto.getRandomValues and crypto.randomUUID. Math.random works. To sign, use Sign JWT with RSA or Cortex.ComputeHMACSHA256.
  • TextEncoder and TextDecoder. Use Encoding.UTF8 for byte work.
  • The file system, process, and require.
  • A console object connected to the logs. Use Log instead. 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.

GlobalExplanation
ScriptResultThe result the script returns to the action.
UtilsCallback-style wrappers over the Cortex functions and XMLHttpRequest. Go to Utilities for more information.
LogLog.Verbose, Log.Info, Log.Warn, and Log.Error write to the Orange Logic server log.
CortexThe underlying Orange Logic functions that Utils wraps, including CallInternalAPI, ExecuteAssetUpload, ImportFromThirdParty, GetCache, StoreCache, SignJWTWithPrivateRSAKe, ComputeHMACSHA256, PublishMessageToAWSSNS, and ExportToS3. Use Utils wherever a wrapper exists.
XMLToolsParses, updates, and deletes XML. Go to Tools for more information.
JsonConvertNewtonsoft 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).
ThreadThread.Sleep(ms) pauses the script.
LockerA distributed lock. Use it through Utils.ExecuteWithLock.
ActionPasses a JavaScript function to a .NET method, as new Action(fn). Utils.ExecuteWithLock uses it.
Base64, btoa, atobBase64 encoding and decoding.
setTimeout, setInterval, clearTimeout, clearIntervalTimers. A timer callback runs only while Orange Logic is still waiting on the script.
PromiseThe native promise, wrapped so that Orange Logic waits for every pending promise before the run ends.
XMLHttpRequestSynchronous or asynchronous HTTP over .NET HttpClient. responseXML is not implemented, and upload is a stub.
FormData, BlobMultipart request bodies for XMLHttpRequest.
URL, URLSearchParamsURL 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:

  1. Substitutes the placeholders into the script text.
  2. Reads the directive block at the top of the script.
  3. Creates a new engine with the heap limits, the globals, and the polyfills, then evaluates the script.
  4. Waits for the script's last expression if it is a promise, then for every pending promise, then for every pending XMLHttpRequest, then for ScriptResult.Data if it is a promise.
  5. Serializes ScriptResult and returns it to the action.
  6. 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:

  • Success determines whether the action succeeded. It defaults to true.
  • Message and Details appear in the failure notification. ScriptResult.Step(text) appends a line to Details, which gives you a trace of what the script did.
  • Data is returned to the action as JSON, and a downstream action reads it from there. Utils.SetScriptOutput(value) sets it as well.
  • StatusCode is 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.ExecuteHttpRequest sets ScriptResult.StatusCode to the status of the last call it made, and sets ScriptResult.Success to false on any status outside the 2xx range. When your script treats a status such as 404 or 409 as a normal outcome, set ScriptResult.Success back 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.

⚠️

Caution

If the script timeout lands while a timer callback is running, the callback stops and the script never ends. Use Thread.Sleep to 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.Success back to true, because the non-2xx status cleared it.
  • An explicit request timeout: Pass Timeout in 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.

SettingDefaultApplies when
Script timeout7,200 seconds, which is two hoursTimeout (seconds) is empty or 0
HTTP request timeout20 minutesA request made with Execute HTTP request or XMLHttpRequest sets no Timeout of its own. An unlimited timeout is not supported.
Maximum retries10Max Retries is empty on an Asynchronous processing script
Wait before a retry1 minute minimum, 24 hours maximumAn asynchronous script retries a failed run
Script heap budget4 MB soft, 8 MB hardEvery script, unless // MaxHeapSizeMB raises it

Did this page help you?