> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Scripting Reference

> Server-side JavaScript scripting with the GoJA runtime for intake scripts, workflow steps, event subscriptions, and option formulas

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

<Note>
  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.
</Note>

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`, `knowledge`, `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.

```javascript theme={null}
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.

```javascript theme={null}
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.

<Tabs>
  <Tab title="Identity and Metadata">
    | Method          | Returns    | Description                           |
    | --------------- | ---------- | ------------------------------------- |
    | `getUUID()`     | `string`   | Unique document identifier            |
    | `getVersion()`  | `string`   | Document format version               |
    | `getMixins()`   | `string[]` | Active mixins (e.g., `"spatial"`)     |
    | `getLabels()`   | `string[]` | All labels on the document            |
    | `getMetadata()` | `object`   | Document metadata key-value pairs     |
    | `getAllTags()`  | `string[]` | All tag names present in the document |
  </Tab>

  <Tab title="Content Nodes">
    | Method                             | Returns                       | Description                         |
    | ---------------------------------- | ----------------------------- | ----------------------------------- |
    | `getContentNode()`                 | `ScriptContentNode`           | Root content node                   |
    | `getRootNode()`                    | `ScriptContentNode`           | Alias for `getContentNode()`        |
    | `select(selector, variables)`      | `ScriptContentNode[]`         | Query content nodes with a selector |
    | `selectFirst(selector, variables)` | `ScriptContentNode` or `null` | First matching content node         |
  </Tab>

  <Tab title="Data Objects">
    | Method                                                  | Returns                      | Description                                |
    | ------------------------------------------------------- | ---------------------------- | ------------------------------------------ |
    | `getAllDataObjects()`                                   | `ScriptDataObject[]`         | All data objects in the document           |
    | `findDataObjectsByPath(path)`                           | `ScriptDataObject[]`         | Data objects matching the given path       |
    | `findFirstDataObjectByPath(path)`                       | `ScriptDataObject` or `null` | First data object matching the path        |
    | `getOrCreate(path, opts?)`                              | `ScriptDataObject`           | Find first object at `path`, or create one |
    | `createDataObject({path, taxonomyRef, sourceOrdering})` | `ScriptDataObject`           | Create a new data object                   |
    | `deleteDataObject(id)`                                  | `void`                       | Remove a data object by ID                 |
  </Tab>

  <Tab title="Mutations">
    | Method                    | Returns | Description                                                                                   |
    | ------------------------- | ------- | --------------------------------------------------------------------------------------------- |
    | `setMetadata(key, value)` | `void`  | Set a metadata entry                                                                          |
    | `addLabel(label)`         | `void`  | Add a label to the document                                                                   |
    | `removeLabel(label)`      | `void`  | Remove a label from the document                                                              |
    | `addTaxonomy(taxonomy)`   | `void`  | Bring a taxonomy into the document's in-scope set so `setAttribute` can resolve types from it |
  </Tab>
</Tabs>

<Warning>
  Do not call `doc.close()` from platform scripts. Kodexa persists modified documents and releases script document resources after the script completes.
</Warning>

### Example: Querying a Document

```javascript theme={null}
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);
}
```

## 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.

<Tabs>
  <Tab title="Navigation">
    | 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          |
  </Tab>

  <Tab title="Read Attributes">
    | 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) |
  </Tab>

  <Tab title="Write">
    | 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                         |
  </Tab>
</Tabs>

<Note>
  `getFirstAttributeValue()` returns the **current typed value**, not the original extracted text. The value precedence is: `stringValue` > `decimalValue` > `booleanValue` > `dateValue` (RFC 3339) > raw `value`.
</Note>

### Example: Reading and Writing Attributes

```javascript theme={null}
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.

<Tabs>
  <Tab title="Read">
    | 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     |
  </Tab>

  <Tab title="Write">
    | 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                                   |
  </Tab>
</Tabs>

<Tip>
  **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.
</Tip>

<Tip>
  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.
</Tip>

<Note>
  **Type resolution.** Both `setAttribute` and `addAttribute` resolve the new attribute's `TypeAtCreation` in the same precedence order:

  1. `opts.type` (advanced override on `addAttribute`)
  2. A runtime-supplied `TaxonResolver` (browser subscriptions wire one)
  3. The document's cached taxonomies — taxonomies travel with the document via the KDDB
  4. A fallback inferred from whichever typed-value field the caller supplied (e.g. `decimalValue` → `DECIMAL`, `stringValue` → `STRING`)

  Scripts can extend the in-scope set at runtime with `document.addTaxonomy(taxonomy)` — the next write call sees the new taxonomy.

  Numeric taxon types — `NUMBER`, `INTEGER`, `DECIMAL`, `CURRENCY`, and `PERCENTAGE` — all share the underlying `DecimalValue` slot and are routed there automatically. Don't pass `type: "DECIMAL"` on numeric writes; the resolver picks the right slot from the taxon.
</Note>

<Warning>
  **`addAttribute` ignores the `path` option.** Attribute paths are derived from the parent data object + tag name, so `path` (if supplied) is discarded and the call logs a warning. Drop `path` from existing `addAttribute` calls. To control where an attribute lives, navigate to the correct parent via `getOrCreateChild(...)` first, then call `addAttribute` on that parent.
</Warning>

### Example: Updating an Attribute

```javascript theme={null}
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.

<Tabs>
  <Tab title="Navigation">
    | 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           |
  </Tab>

  <Tab title="Tags and Features">
    | 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 |
  </Tab>

  <Tab title="Selectors">
    | Method                             | Returns                       | Description                       |
    | ---------------------------------- | ----------------------------- | --------------------------------- |
    | `select(selector, variables)`      | `ScriptContentNode[]`         | Query descendants with a selector |
    | `selectFirst(selector, variables)` | `ScriptContentNode` or `null` | First matching descendant         |
  </Tab>

  <Tab title="Spatial">
    | Method             | Returns                           | Description                      |
    | ------------------ | --------------------------------- | -------------------------------- |
    | `getBoundingBox()` | `{x, y, width, height}` or `null` | Spatial bounding box coordinates |
  </Tab>

  <Tab title="Mutations">
    | 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              |
  </Tab>
</Tabs>

### Example: Searching Content Nodes

```javascript theme={null}
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());
  }
}
```

## 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.

```javascript theme={null}
// 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 inside Activity Plans and can load documents, call service bridges, invoke LLMs, and read the knowledge resolved on the in-scope family.

```javascript theme={null}
// 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");
}
```

<Tip>
  For dispatch logic driven by upstream knowledge resolution (vendor routing, processing-model selection, etc.), use the `knowledge` global instead of re-deriving it from data objects. See [Knowledge Bindings](/guides/scripting/knowledge-bindings).
</Tip>

### 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.

```javascript theme={null}
// 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

Selection option formulas compute dynamic dropdown values for data form fields. They can call service bridges and reference attributes.

```javascript theme={null}
// 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

<Tip>
  **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`.

  ```javascript theme={null}
  if (!currentObject) return;
  var val = currentObject.getFirstAttributeValue("field") || "default";
  ```
</Tip>

<Tip>
  **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.
</Tip>

<Tip>
  **Keep scripts focused.** Each script should have a single, clear purpose. Complex logic is better split across multiple steps or event subscriptions.
</Tip>

<Warning>
  **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.
</Warning>

<Warning>
  **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.
</Warning>

<Note>
  **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.
</Note>

### Common Patterns

#### Null-safe Attribute Access

```javascript theme={null}
var value = obj.getFirstAttributeValue("optional_field") || "N/A";
```

#### Iterating Children

```javascript theme={null}
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.

```javascript theme={null}
obj.setAttribute("computed_flag", true);
```

#### Date Comparisons

```javascript theme={null}
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

<Warning>
  **Script timeout exceeded** -- Your script is taking too long. Reduce iterations, avoid loading large documents unnecessarily, and move complex logic to module-based processing.
</Warning>

<Warning>
  **`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.
</Warning>

<Warning>
  **`null` reference errors** -- Always check return values before calling methods on them. `getAttributeByName()`, `getParent()`, `selectFirst()`, and similar methods can return `null`.
</Warning>

<Warning>
  **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.
</Warning>
