JavaScript Scripts
JavaScript scripts execute custom logic in a sandboxed JavaScript engine. A script can be defined inline or loaded from a .js file in the repository.
Definition
{
"name": "calculatePremium",
"type": "JAVASCRIPT",
"scope": "OBJECT",
"trigger": ["UPDATE"],
"javascriptFile": "scripts/calculatePremium.js"
}
| Property | Type | Description |
|---|---|---|
apiVersion | integer | The script API version the script is written for. Default 1. |
javascriptFile | string | Path to the .js file, relative to the repository root, including the extension. |
javascript | string | Inline JavaScript source. Use javascriptFile for anything non-trivial. |
utilitiesFiles | string[] | Shared utility files prepended before the main script, with their .js extension. |
Inline vs file-based
For short scripts, define the JavaScript inline with javascript. For anything non-trivial, use javascriptFile to reference a .js file in the repository:
my-project/
├── index.json
├── scripts/
│ └── calculatePremium.js
{
"name": "calculatePremium",
"type": "JAVASCRIPT",
"scope": "OBJECT",
"trigger": ["UPDATE"],
"javascriptFile": "scripts/calculatePremium.js"
}
Shared utility files
When multiple scripts share helper functions, repeating the same code in every file creates a maintenance burden. Use utilitiesFiles to declare one or more shared .js files that are prepended to the main script before execution.
{
"name": "createInvoices",
"type": "JAVASCRIPT",
"utilitiesFiles": ["_utils.js"],
"javascriptFile": "scripts/createInvoices.js"
}
File layout:
my-project/
├── index.json
├── _utils.js
└── scripts/
├── createInvoices.js
└── createBudgetIfMissing.js
The platform concatenates all files in utilitiesFiles in order and prepends the result before the main script. Functions defined in utility files are available in the global scope — no import syntax required.
A utility file is a plain .js file. It must not define an execute function:
// _utils.js
function toTenantDateParts(isoString, timezone) {
var d = new Date(new Date(isoString).toLocaleString('en-US', { timeZone: timezone }));
return { year: d.getFullYear(), month: d.getMonth() + 1, day: d.getDate() };
}
The main script calls the helper directly:
// scripts/createInvoices.js
function execute(api, dataObject, params) {
var tz = api.getTimeZone();
var parts = toTenantDateParts(dataObject.invoiceDate, tz);
// ...
}
Multiple utility files are loaded in the listed order and may reference each other when declared in the right sequence.
Function signature
Every script file must define a function named execute:
function execute(api, dataObject, params) {
// your logic here
}
| Argument | Description |
|---|---|
api | The script API — read and write data, log messages, send notifications. See below. |
dataObject | The current object as a plain JavaScript map; see The dataObject. null for a GENERAL-scope script. |
params | Resolved script parameters as a key-value map. |
The dataObject
The dataObject holds the object's field values under their field names, and its relations under their REST names (singular for a single-valued relation, plural for a multiple-valued one), each as an object URI. On top of that it has the platform's own properties, which start with _:
| Property | Description |
|---|---|
_reference | The object's URI, e.g. /companies/{id}. Pass it directly to API methods such as api.update. |
_classes | The plural REST names of all the object's classes, e.g. ["companies", "entities", "items"]. |
_creationDate | When the object was created (ISO-8601, UTC). |
_modificationDate | When the object was last changed (ISO-8601, UTC). |
Objects returned by api.get, api.query and api.getRelatedObjects have the same shape.
The api object
Reading data
api.get(uri)
Returns a single object by URI, or null if not found.
var company = api.get(dataObject.company);
api.query(typePluralName, filters)
api.query(typePluralName, filters, sortBy)
Returns all objects of a given type matching the filters. filters is a key-value map of field names to values — pass null for no filters. Optionally sort by a field name.
var activeContracts = api.query('employmentAgreements', { status: 'ACTIVE' });
var sorted = api.query('invoices', null, 'invoiceDate');
api.getRelatedObjects(uri, fromTypePluralName, typePluralName)
api.getRelatedObjects(uri, fromTypePluralName, typePluralName, filters, sortBy)
Returns all related objects of a given type for the object at uri. fromTypePluralName is the type that owns the relation definition — a data object can have multiple types, and the relation may be defined on a different type than the one in the URI.
var lines = api.getRelatedObjects(
dataObject._reference,
'invoices', // type that owns the 'lines' relation
'invoiceLines' // type to retrieve
);
With filters and sorting:
var expenseLines = api.getRelatedObjects(
dataObject._reference,
'invoices',
'invoiceLines',
{ type: 'EXPENSE' },
'amount'
);
Writing data
api.create(typePluralName, data)
Creates a new object and returns the created record as a map.
var task = api.create('tasks', {
name: 'Follow up with ' + dataObject.name,
dueDate: dataObject.expiryDate,
assignedTo: dataObject.accountManager
});
api.update(uri, data)
Patches an existing object. Only the provided fields are updated; all other fields are left unchanged.
api.update(dataObject._reference, {
status: 'COMPLETED',
completedDate: new Date().toISOString().slice(0, 10)
});
api.saveMany(records)
Creates or updates multiple objects in a single batch. Each entry must have either:
_reference— patches the existing object at that URI_class— creates a new object of that class, by plural REST name
Background recalculations are deduplicated across the batch, making this more efficient than calling create / update in a loop.
// Batch update
var updates = lines.map(function(line) {
return { _reference: line._reference, processed: true };
});
api.saveMany(updates);
// Batch create
api.saveMany([
{ _class: 'tasks', name: 'Task A', dueDate: '2025-06-01' },
{ _class: 'tasks', name: 'Task B', dueDate: '2025-06-15' }
]);
api.remove(uri)
Deletes the object at the given URI.
api.remove(obsoleteTask._reference);
Utilities
api.runScript(scriptName, dataObjectMap, parameters)
Runs another script by name. Pass the object map for OBJECT-scope scripts, or null for GENERAL-scope scripts.
api.runScript('sendConfirmationEmail', dataObject, { template: 'welcome' });
api.produce(sourceName, params)
Calls a producer script by name and returns its results as a list of maps. params is a key-value map of the parameters to pass. Exceptions thrown by the producer (e.g. unsupported country code) propagate to the calling script and can be caught for fallback logic.
var results = api.produce('CompanySearch', { countryCode: 'NL', query: dataObject.name });
if (results.length > 0) {
api.update(dataObject._reference, { companyKey: results[0].key });
}
api.createBinary(fileName, contentType, content)
Creates a binary file from a UTF-8 string and returns an object describing it: { href, fileName, contentType, size }. Assign the returned object as-is to a FILE field in api.create or api.update; the platform then attaches the file to the object.
var csv = 'name,amount\nLine A,100\nLine B,200';
var file = api.createBinary('export.csv', 'text/csv', csv);
api.update(dataObject._reference, { exportFile: file });
api.stripHtml(html)
Removes HTML tags and decodes HTML entities. Useful when processing TEXTBLOCK values before including them in generated documents.
var plainText = api.stripHtml(dataObject.description);
api.getTimeZone()
Returns the tenant's configured timezone as an IANA timezone string (e.g. "Europe/Amsterdam", "America/New_York").
Use this when your script performs calendar-based date arithmetic — for example, computing "today", "start of month", or "end of quarter" in the tenant's local time. new Date() always returns UTC, so without api.getTimeZone() the result will be wrong for any tenant not on UTC.
var tz = api.getTimeZone(); // e.g. "Europe/Amsterdam"
api.log(message)
Logs a message to the server log, visible in application logs.
api.log('Processing ' + dataObject._reference);
api.notify(message, level)
Sends a notification to the user who triggered the script. Levels: INFO, WARNING, ERROR.
api.notify('No matching records found.', 'WARNING');
api.redirect(uri)
Tells the client to navigate to the given URI after the script completes. Used in GENERAL-scope scripts invoked from a SOURCE layout item — after creating or updating an object, the client navigates to the result.
var company = api.create('companies', { name: params.name });
api.redirect(company._uri);
api.redirect() and api.notify() are independent — you can call both, either, or neither. When both are set, both are included in the response.
Example: set a completion date on status change
A script on my-project.task that records the date when a task is completed. triggerValues lists the fields to watch, so the script only runs when status was modified. The precondition narrows that to updates where the status is now COMPLETED.
{
"name": "setCompletedDate",
"type": "JAVASCRIPT",
"scope": "OBJECT",
"trigger": ["UPDATE"],
"triggerValues": ["status"],
"preconditions": ["status = 'COMPLETED'"],
"javascriptFile": "scripts/setCompletedDate.js"
}
function execute(api, dataObject, params) {
if (dataObject.completedDate) return; // already set, don't overwrite
api.update(dataObject._reference, {
completedDate: new Date().toISOString().slice(0, 10)
});
}
Example: recalculate a total from related objects
If the total only depends on field values of related objects, a formula using sum is simpler and more efficient — it recalculates automatically without a manual trigger or script overhead.
Use a JavaScript script for this pattern only when the logic cannot be expressed as a formula, for example when the calculation involves external data or conditional branching that cannot be established using sumIf.
A script on my-project.invoice that sums the amounts of all invoice lines and writes the total back to the parent invoice.
function execute(api, dataObject, params) {
var lines = api.getRelatedObjects(
dataObject._reference,
'invoices',
'invoiceLines'
);
var total = 0;
for (var i = 0; i < lines.length; i++) {
total += lines[i].amount ? lines[i].amount.amount : 0;
}
api.update(dataObject._reference, {
totalAmount: { amount: total, currency: 'EUR' }
});
api.log('Invoice total updated: ' + total);
}
Example: manual action with parameters
A script triggered manually by the user. Parameters are resolved from the current object before the script runs.
{
"name": "sendReminder",
"type": "JAVASCRIPT",
"scope": "OBJECT",
"trigger": ["MANUAL"],
"javascriptFile": "scripts/sendReminder.js",
"parameters": [
{ "name": "recipientEmail", "dataType": "EMAIL", "formula": "emailAddress" },
{ "name": "dueDate", "dataType": "DATE", "formula": "expiryDate" }
],
"preconditions": ["emailAddress != null"]
}
function execute(api, dataObject, params) {
var email = params.recipientEmail;
var due = params.dueDate;
if (!email) {
api.notify('No email address on this record.', 'WARNING');
return;
}
api.create('reminderLogs', {
sentTo: email,
sentAt: new Date().toISOString(),
dueDate: due,
contract: dataObject._reference
});
api.notify('Reminder sent to ' + email, 'INFO');
}
Example: compute current-month boundaries in tenant timezone
new Date() returns the current moment in UTC. For tenants in a timezone ahead of UTC, an event that fires at 23:00 UTC may already be the next calendar day locally. This example creates a budget for the current month in the tenant's local calendar.
function execute(api, dataObject, params) {
var tz = api.getTimeZone();
var tenantNow = new Date(new Date().toLocaleString('en-US', { timeZone: tz }));
var from = new Date(tenantNow.getFullYear(), tenantNow.getMonth(), 1);
var to = new Date(tenantNow.getFullYear(), tenantNow.getMonth() + 1, 1);
api.create('budgets', {
from: from.toISOString().slice(0, 10),
to: to.toISOString().slice(0, 10),
agreement: dataObject._reference
});
}
toLocaleString('en-US', { timeZone: tz }) produces a locale string that, when parsed back via new Date(), gives a Date object whose .getFullYear(), .getMonth(), and .getDate() reflect the tenant's wall-clock time. The ISO date string is then sliced to YYYY-MM-DD for storage.
Script API versions
The script API is versioned, so that a script keeps working when the platform's API evolves. A script
declares the version it is written for with apiVersion; without it, a script uses version 1. The
JavaScript itself is never converted. See
Versioning and Compatibility.
Scripts from class files in definition format 0 are converted with apiVersion: 0; version 0 is the
same API as version 1.
Version 1
Version 1 consists of exactly these entry points and methods. Nothing else is reachable from a script: methods not in this list, and Java classes and objects, are not available.
| Entry point | |
|---|---|
execute(api, dataObject, params) | JAVASCRIPT scripts |
produce(api, params) | JAVASCRIPT_PRODUCER scripts; see Sources |
| Method | |
|---|---|
api.get(uri) | Reading data |
api.query(typePluralName, filters), api.query(typePluralName, filters, sortBy) | |
api.getRelatedObjects(uri, fromTypePluralName, typePluralName), api.getRelatedObjects(uri, fromTypePluralName, typePluralName, filters, sortBy) | |
api.create(typePluralName, data) | Writing data |
api.update(uri, data) | |
api.saveMany(records) | |
api.remove(uri) | |
api.runScript(scriptName, dataObjectMap, parameters) | Utilities |
api.produce(sourceName, params) | |
api.createBinary(fileName, contentType, content) | |
api.stripHtml(html) | |
api.getTimeZone() | |
api.log(message), api.getLogs() | |
api.notify(message, level) | |
api.redirect(uri) | |
api.issueApiKey(href) | Reserved for the platform's own system.apiKey scripts. |
Lists and maps returned by these methods behave like JavaScript arrays and objects: index them, read
length, use forEach, map and filter, and read or set properties by name. Object.keys() and
JSON.stringify() do not see the properties of a returned object, however; read them by name.