The script API was modernised in 2026.4: methods are now camelCase, bridge.data.* is replaced by currentObject.setAttribute / currentObject.getFirstAttributeValue, and helpers like getOrCreate, payload, and structured log.* reduce boilerplate. Scripts using the legacy API will fail with clear errors. The canonical contract lives in pkg/scripting/SPEC.md.
Kodexa uses GoJA, a Go-based JavaScript engine, for server-side scripting across the platform. Scripts can validate uploads, route workflows, react to data changes, and compute dynamic dropdown options.
Runtime Characteristics
GoJA supports ES5 with partial ES6 features including arrow functions, template literals, let/const, and destructuring. However, it is synchronous only — there is no support for async/await, Promises, or event loops.
Key constraints:
- Sandboxed — no filesystem access, no network access (except via service bridges)
- No module system — no
require() or import
- Synchronous — all calls are blocking; no callbacks or timers
- Deterministic — same inputs always produce same outputs
Execution Contexts
Scripts run in different contexts depending on where they are configured. Each context has its own timeout, globals, and intended purpose.
| Context | Timeout | Use Case | Available Globals |
|---|
| Intake Scripts | 5 seconds | Validate and route uploads | filename, fileSize, mimeType, metadata, document, log |
| Script Steps | 15 seconds | Plan workflow routing | task, families, loadDocument(), serviceBridge, llm, log |
| Event Subscriptions | 2 seconds | React to attribute changes | currentObject, document, event, serviceBridge, log |
| Selection Option Formulas | 2 seconds | Compute dropdown options | serviceBridgeCall(), attribute references |
Core Globals
These functions are available in all scripting contexts.
log
Write messages to the platform execution log via leveled methods. Each method is variadic and joins arguments with spaces.
log.info("Processing document");
log.debug("Current value:", someValue);
log.warn("Missing expected attribute");
log.error("Validation failed for field:", fieldName);
Supported methods: log.debug, log.info, log.warn, log.error.
console.log()
A convenience wrapper that joins arguments with a space and writes at debug level.
console.log("Document UUID:", doc.getUUID());
console.log("Found", results.length, "matches");
Document API — ScriptDocument
The ScriptDocument object represents a loaded Kodexa document (KDDB). It is available as document in intake scripts and returned by loadDocument() in script steps.
Example: Querying a Document
var doc = loadDocument(family.documentVersion);
var invoices = doc.findDataObjectsByPath("Invoice");
for (var i = 0; i < invoices.length; i++) {
var inv = invoices[i];
var total = inv.getFirstAttributeValue("total_amount");
log.info("Invoice total:", total);
}
doc.close();
Data Object API — ScriptDataObject
Data objects represent extracted entities (invoices, line items, claims) within a document. They form a tree structure with parent-child relationships.
Navigation
Read Attributes
Write
| Method | Returns | Description |
|---|
getPath() (or path property) | string | Object path (e.g., "Invoice/LineItems") |
getParent() | ScriptDataObject or null | Parent data object |
getChildren() | ScriptDataObject[] | All direct children |
getChildrenByPath(path) | ScriptDataObject[] | Children matching the given path |
hasChild(path) | boolean | Whether any direct child matches the given path |
hasTaxonomy() | boolean | Whether a taxonomy reference is set |
getID() (or id property) | number | Numeric data-object ID |
getIDString() (or idString property) | string | String representation of the object ID |
| Method | Returns | Description |
|---|
getAttributes() | ScriptDataAttribute[] | All attributes on this object |
getAttributeByName(name) | ScriptDataAttribute or null | First attribute matching the tag name |
getAttributesByName(name) | ScriptDataAttribute[] | All attributes matching the tag name |
getFirstAttributeValue(name) | typed value or undefined | Current typed value of the first matching attribute |
getAttributeValues(name) | string[] | All string values for the named attribute |
payload(mapping) | object | Build a JS object from named attributes (helper for service-bridge bodies) |
| Method | Returns | Description |
|---|
setAttribute(name, value) | void | Find-or-create an attribute and write the value (type-aware) |
addAttribute({tag, value, stringValue, decimalValue, booleanValue, ...}) | ScriptDataAttribute | Add a new attribute |
addChild({path, sourceOrdering?, taxonomyRef?}) | ScriptDataObject | Append a child data object |
getOrCreateChild(path, opts?) | ScriptDataObject | Find first child at path, or create one |
copyAttributeFrom(srcAttr, opts?) | ScriptDataAttribute | Copy a single attribute from another object |
copyAttributesFrom(srcObject, mappings, ownerUri?) | void | Copy multiple attributes in one call |
getFirstAttributeValue() returns the current typed value, not the original extracted text. The value precedence is: stringValue > decimalValue > booleanValue > dateValue (RFC 3339) > raw value.
Example: Reading and Writing Attributes
if (!currentObject) return;
// Read an attribute
var status = currentObject.getFirstAttributeValue("claim_status");
log.info("Current status:", status);
// Compute and write a new attribute (find-or-create, type-aware)
var amount = currentObject.getFirstAttributeValue("line_amount") || 0;
var tax = currentObject.getFirstAttributeValue("tax_rate") || 0;
currentObject.setAttribute("total_with_tax", amount * (1 + tax / 100));
Data Attribute API — ScriptDataAttribute
Data attributes store individual typed values on a data object. Each attribute has a tag name, optional typed values, and audit trail tracking.
| Method | Returns | Description |
|---|
getTag() | string | Attribute name |
getPath() | string | Full attribute path |
getValue() | typed value | Typed scalar value |
getStringValue() | string | Typed string value |
getDecimalValue() | number | Typed decimal value |
getBooleanValue() | boolean | Typed boolean value |
getDateValue() | string | RFC 3339 date string |
getConfidence() | number | Optional confidence score |
getOwnerUri() | string | Audit-trail owner URI |
| Method | Returns | Description |
|---|
setValue(value) | void | Write the value, coerced to the attribute’s TypeAtCreation |
setStringValue(string) | void | Set the typed string value |
setDecimalValue(number) | void | Set the typed decimal value |
setBooleanValue(boolean) | void | Set the typed boolean value |
setDateValue(rfc3339String) | void | Set the typed date value |
No-op suppression: Writes that do not actually change the value are automatically suppressed and will not trigger downstream recalculation. This uses epsilon comparison for decimals (1e-9) and time.Equal() for dates. You do not need to guard against redundant writes.
For most write paths, prefer currentObject.setAttribute(name, value) over fetching the attribute first and calling typed setters. setAttribute finds-or-creates the attribute and picks the type-appropriate setter automatically.
Type resolution. When setAttribute creates a new attribute, it picks the typed slot in this order: a runtime-supplied TaxonResolver (browser subscriptions wire one), then the document’s cached taxonomies (taxonomies travel with the document via the KDDB), then a fallback inferred from the JS value’s type. Scripts can extend the in-scope set at runtime with document.addTaxonomy(taxonomy) — the next setAttribute call sees the new taxonomy.
Example: Updating an Attribute
var attr = currentObject.getAttributeByName("invoice_total");
if (attr) {
var current = attr.getDecimalValue();
if (current > 10000) {
currentObject.setAttribute("review_flag", "HIGH VALUE");
log.info("Flagged high-value invoice:", current);
}
}
Content Node API — ScriptContentNode
Content nodes represent the structural elements of a document’s content tree: pages, lines, words, tables, and cells.
Navigation
Tags and Features
Selectors
Spatial
Mutations
| Method | Returns | Description |
|---|
getContent() | string | Text content of this node |
getNodeType() | string | Node type ("line", "word", "page", etc.) |
getChildren() | ScriptContentNode[] | Direct child nodes |
getParent() | ScriptContentNode or null | Parent node |
getDescendants() | ScriptContentNode[] | All descendants (recursive) |
getAncestors() | ScriptContentNode[] | All ancestors up to root |
getAllContent(separator, strip) | string | Concatenated text from all descendants |
getPage() | number | Page number this node belongs to |
getIDString() | string | String representation of the node ID |
| Method | Returns | Description |
|---|
getTags() | Tag[] | All tags on this node |
hasTag(tagName) | boolean | Check if a tag is present |
getFeatures() | Feature[] | All features on this node |
hasFeature(featureType, name) | boolean | Check if a feature exists |
getFeatureValue(featureType, name) | any | Get a feature’s value |
getConfidence() | number | Extraction confidence score |
hasConfidence() | boolean | Whether a confidence score is set |
| Method | Returns | Description |
|---|
select(selector, variables) | ScriptContentNode[] | Query descendants with a selector |
selectFirst(selector, variables) | ScriptContentNode or null | First matching descendant |
| Method | Returns | Description |
|---|
getBoundingBox() | {x, y, width, height} or null | Spatial bounding box coordinates |
| Method | Returns | Description |
|---|
tag(tagName, {value, confidence, groupUuid, index}) | void | Add a tag to this node |
removeTag(tagName) | void | Remove a tag |
addFeature(featureType, name, value) | void | Add a feature |
setFeature(featureType, name, value) | void | Set a feature (create or update) |
removeFeature(featureType, name) | void | Remove a feature |
setContent(text) | void | Update the text content |
addChild(nodeType, content) | ScriptContentNode | Create a child node |
removeChild(child) | void | Remove a child node |
Example: Searching Content Nodes
var doc = loadDocument(family.documentVersion);
var root = doc.getContentNode();
// Find all lines on page 1
var lines = root.select("//line[hasTag('kodexa-spatial')]", {});
for (var i = 0; i < lines.length; i++) {
if (lines[i].getPage() === 1) {
log.info("Page 1 line:", lines[i].getContent());
}
}
doc.close();
Context-Specific Details
Intake Scripts
Intake scripts run when a file is uploaded to a document store. Use them to validate, classify, or route incoming files.
// Available globals
log.info("File:", filename);
log.info("Size:", fileSize, "bytes");
log.info("Type:", mimeType);
// Access upload metadata
var source = metadata["source"] || "unknown";
// Reject files that are too large
if (fileSize > 50 * 1024 * 1024) {
log.error("File exceeds 50MB limit");
throw new Error("File too large");
}
Script Steps
Script steps run within assistant workflows and can load documents, call service bridges, and invoke LLMs.
// Iterate over document families in the task
for (var i = 0; i < families.length; i++) {
var family = families[i];
var doc = loadDocument(family.documentVersion);
var dataObjects = doc.getAllDataObjects();
log.info("Found", dataObjects.length, "data objects");
doc.close();
}
Event Subscriptions
Event subscriptions run in response to attribute changes on data objects within a taxonomy. They are configured on the Event Subscriptions tab of the taxonomy editor.
// Always guard against null
if (!currentObject) return;
// Read the changed attribute
var amount = currentObject.getFirstAttributeValue("invoice_amount");
// Call an external service via bridge — payload() builds the body
// in one line from named attributes
var result = serviceBridge.call(
"myorg/validation-api",
"validate",
currentObject.payload({ amount: "invoice_amount" })
);
if (result && result.valid) {
currentObject.setAttribute("status", "Validated");
}
Selection option formulas compute dynamic dropdown values for data form fields. They can call service bridges and reference attributes.
// Call a service bridge to fetch options
var response = serviceBridgeCall("lookup-bridge", "/api/options");
var items = JSON.parse(response);
// Return an array of {label, value} objects
var options = [];
for (var i = 0; i < items.length; i++) {
options.push({
label: items[i].name,
value: items[i].code
});
}
return options;
Best Practices
Guard against null values. In event subscriptions, always check that currentObject exists before accessing it. In any context, attribute lookups may return null or undefined.if (!currentObject) return;
var val = currentObject.getFirstAttributeValue("field") || "default";
Prefer setAttribute for writes. currentObject.setAttribute(name, value) finds-or-creates the attribute and routes through the type-appropriate setter. Reach for the typed setters (setStringValue, setDecimalValue, etc.) only when you already hold an attribute reference and need fine-grained control.
Keep scripts focused. Each script should have a single, clear purpose. Complex logic is better split across multiple steps or event subscriptions.
Be aware of timeouts. Intake scripts have 5 seconds, script steps have 15 seconds, and event subscriptions and option formulas have only 2 seconds. Avoid unnecessary loops or redundant document loads.
No async operations. GoJA does not support Promises, async/await, setTimeout, or setInterval. All code runs synchronously. If you need to call an external API, use the serviceBridge global.
Debugging tip: Use log.debug(...) liberally during development. Debug-level messages appear in the execution log but do not affect production behavior. Remove or reduce logging once the script is stable.
Common Patterns
Null-safe Attribute Access
var value = obj.getFirstAttributeValue("optional_field") || "N/A";
Iterating Children
var lineItems = parentObj.getChildrenByPath("LineItem");
var total = 0;
for (var i = 0; i < lineItems.length; i++) {
var amount = lineItems[i].getFirstAttributeValue("amount") || 0;
total += amount;
}
log.info("Computed total:", total);
Conditional Attribute Creation
setAttribute is idempotent — it finds-or-creates the attribute. There is no need to check whether it already exists.
obj.setAttribute("computed_flag", true);
Date Comparisons
var dateStr = obj.getFirstAttributeValue("due_date");
if (dateStr) {
var due = new Date(dateStr);
if (due < new Date()) {
log.warn("Past due:", dateStr);
obj.setAttribute("overdue", true);
}
}
Troubleshooting
Script timeout exceeded — Your script is taking too long. Reduce iterations, avoid loading large documents unnecessarily, and move complex logic to module-based processing.
undefined is not a function — You are calling a method that does not exist in the GoJA runtime. Check for typos and verify the method is available in the API tables above. Remember: no Array.map(), Array.filter(), or other ES6+ array methods.
null reference errors — Always check return values before calling methods on them. getAttributeByName(), getParent(), selectFirst(), and similar methods can return null.
Writes not taking effect — Prefer currentObject.setAttribute(name, value) for writes; it routes through the type-appropriate setter and triggers downstream recalculation correctly. The no-op suppression logic only applies to typed values, so writing through setValue() with a stale type may silently no-op.