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"
}
| Property | Type | Description |
|---|---|---|
javascriptFile | string | Path to a .js file in the repository (without extension). |
javascript | string | Inline JavaScript source. Use javascriptFile for anything non-trivial. |
utilitiesFiles | string[] | Shared utility files prepended before the main script. Listed without .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"
}
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"],
"javascriptFile": "scripts/createInvoices"
}
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. All field values are accessible by their camelCase names. The special field _reference holds the object's URI and can be passed directly to API methods. |
params | Resolved script parameters as a key-value map. |
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_type— creates a new object of that plural type 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([
{ _type: 'tasks', name: 'Task A', dueDate: '2025-06-01' },
{ _type: '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 its URI. The returned URI can be assigned directly to a FILE-typed field.
var csv = 'name,amount\nLine A,100\nLine B,200';
var fileUri = api.createBinary('export.csv', 'text/csv', csv);
api.update(dataObject._reference, { exportFile: fileUri });
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. Using triggerValues ensures it only runs when the status field changes to COMPLETED.
{
"name": "setCompletedDate",
"type": "javascript",
"scope": "OBJECT",
"trigger": "UPDATE",
"triggerValues": ["COMPLETED"],
"javascriptFile": "scripts/setCompletedDate"
}
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",
"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.