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

# Event-Based Scripting

> Attach reactive JavaScript to Kodexa data definitions so data changes derive values, call service bridges, manage exceptions, and update related fields.

Event-based scripting belongs to **Data Definitions**. It is how the data model reacts when values change.

A Data Form renders the review experience for a Task. A Data Definition owns the structure and behavior of the business data being reviewed. Event subscriptions let that data model run JavaScript when an event occurs, such as an attribute changing on a data object.

Use event scripts when a change to one field should update other fields, call an external system, create an exception, emit another event, or apply business logic that is too procedural for a formula.

## When To Use Event Scripts

Use an event subscription when the data definition needs behavior, not just structure:

* Derive several sibling attributes after a reviewer edits one value
* Normalize data before formulas, validation, and selection rules run
* Call a Service Bridge to enrich a row from an external system
* Create or close data exceptions based on business logic
* Copy or move data objects when a modeled relationship changes
* Emit a named event that another subscription can handle

Use a formula when one output value can be computed declaratively from other values. Use event scripting when the change has side effects or needs multiple reads and writes.

## Where Event Scripts Run

Event subscriptions are attached to **group data elements** in a Data Definition. In configuration, those group elements are stored as `taxons`. At runtime, the script executes for one data object instance of that group.

```mermaid theme={null}
flowchart LR
  Edit["Reviewer edits attribute"] --> Event["changed:dataAttribute:field_name"]
  Event --> Sub["Group event subscription"]
  Sub --> Script["Subscription script"]
  Script --> Writes["Update attributes or objects"]
  Writes --> Recalc["Formula, selection, validation, formatting"]
```

The script runs inside the recalculation flow. Writes made by the script are visible to downstream formula recalculation, selection option evaluation, validation, selection validation, and conditional formatting.

## Configuration

Attach `eventSubscriptions` to a group data element:

```yaml theme={null}
- name: shipment
  label: Shipment
  group: true
  eventSubscriptions:
    - name: compute-accessorial-total
      on: "changed:dataAttribute:(quantity|unit_price)"
      script: |
        if (!currentObject) return;

        var quantity = currentObject.getFirstAttributeValue("quantity") || 0;
        var unitPrice = currentObject.getFirstAttributeValue("unit_price") || 0;

        currentObject.setAttribute("line_total", quantity * unitPrice);
  children:
    - name: quantity
      label: Quantity
      taxonType: NUMBER
    - name: unit_price
      label: Unit Price
      taxonType: NUMBER
    - name: line_total
      label: Line Total
      taxonType: NUMBER
```

### Attribute Change Shorthand

For simple attribute-change subscriptions, `dependsOn` is a compact way to list the attributes that should trigger the script:

```yaml theme={null}
eventSubscriptions:
  - name: compute-accessorial-total
    on: "changed:dataAttribute"
    dependsOn:
      - quantity
      - unit_price
    script: |
      currentObject.setAttribute("line_total", 0);
```

Kodexa expands that into the equivalent event pattern:

```yaml theme={null}
on: "changed:dataAttribute:(quantity|unit_price)"
```

Use the explicit `on` form when you need to match multiple event types or a custom event pattern.

### Fields

<ParamField path="name" type="string" required>
  Unique name for the subscription within the owning group data element. The runtime uses this name for loop-control state and diagnostics.
</ParamField>

<ParamField path="on" type="string" required>
  Regex pattern matched against the full event string. Kodexa anchors the pattern internally, so `changed:dataAttribute:amount` matches only that event string.
</ParamField>

<ParamField path="dependsOn" type="string[]">
  Convenience field for `changed:dataAttribute`. When present with `on: "changed:dataAttribute"`, validation converts it to an `on` regex and clears `dependsOn`.
</ParamField>

<ParamField path="script" type="string" required>
  JavaScript source executed for the subscription run.
</ParamField>

<ParamField path="disabled" type="boolean" default="false">
  When `true`, the subscription remains in the Data Definition but is skipped at runtime.
</ParamField>

## Supported Event Strings

The subscription matcher uses full event strings.

| Event string                       | How it is used                                                                         |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| `changed:dataAttribute:field_name` | Fired when a data attribute changes. This is the main automatic data-definition event. |
| `created:dataObject`               | Fired by the document runtime when a data object is created.                           |
| `loaded:dataObject`                | Fired after initial document load in the browser runtime.                              |
| `formLoaded:form_ref`              | Matches form-load events emitted by the host.                                          |
| `trigger:name`                     | Matches named events emitted by script or host code.                                   |
| `focus:dataAttribute:field_name`   | Matchable event for focus-aware hosts.                                                 |
| `blur:dataAttribute:field_name`    | Matchable event for blur-aware hosts.                                                  |

For common attribute-change subscriptions, use:

```yaml theme={null}
on: "changed:dataAttribute:(shipper_city|shipper_state|postal_code)"
```

For a named event:

```yaml theme={null}
on: "trigger:recalculate-rating"
```

For multiple event types:

```yaml theme={null}
on: "(loaded:dataObject|trigger:recalculate-rating)"
```

## Runtime Rules

| Rule             | Detail                                                                                  |
| ---------------- | --------------------------------------------------------------------------------------- |
| Runtime          | JavaScript VM                                                                           |
| Timeout          | 2 seconds per subscription execution                                                    |
| VM lifetime      | Fresh VM per subscription execution                                                     |
| Async            | No `async` / `await`; calls are synchronous                                             |
| Document scope   | Use the prebound `document`; `loadDocument()` is not supported for subscription scripts |
| Attribute writes | Use `currentObject.setAttribute(name, value)`                                           |
| Type safety      | Typed setters validate against the attribute type                                       |

## Execution Order

When a data attribute changes, Kodexa processes the cascade in this order:

```text theme={null}
1. Mark the attribute dirty
2. Emit the attribute-changed event
3. Run matching event subscriptions
4. Recalculate dependent formulas
5. Re-evaluate affected selection option formulas
6. Evaluate affected validations
7. Validate selections
8. Evaluate affected conditional formats
```

This ordering matters. If an event script writes `tax_rate`, formulas and validations that depend on `tax_rate` see the value written by the script.

## Script Globals

Every event subscription receives a small set of prebound globals.

| Global          | Available                                     | Use                                                                                                         |
| --------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `currentObject` | All subscription scripts                      | The group data object instance that owns the event. May be `null`; guard before use.                        |
| `document`      | All subscription scripts                      | The active document containing `currentObject`.                                                             |
| `event`         | All subscription scripts                      | Event payload with the event string, changed attribute, old value, new value, and data object id.           |
| `serviceBridge` | Subscription runtimes with platform transport | Calls configured Service Bridges through the platform proxy.                                                |
| `taxon`         | Browser subscription runtime                  | Looks up selection option labels from loaded Data Definitions.                                              |
| `bridge`        | Browser subscription runtime                  | UI/event hooks such as notifications and explicit event firing. Guard before use for cross-runtime scripts. |
| `log`           | All subscription scripts                      | Structured logging with `debug`, `info`, `warn`, and `error`.                                               |
| `console`       | All subscription scripts                      | Browser-style logging aliases.                                                                              |

### `event`

The `event` object describes why the script ran.

| Property        | Type   | Description                                                              |
| --------------- | ------ | ------------------------------------------------------------------------ |
| `type`          | string | Full event string, such as `changed:dataAttribute:amount`                |
| `eventKind`     | string | Event prefix, such as `changed:dataAttribute`, when supplied by the host |
| `attributeTag`  | string | Changed attribute name for attribute events                              |
| `attributePath` | string | Full attribute path when available                                       |
| `dataObjectId`  | number | ID of the data object where the event occurred                           |
| `oldValue`      | any    | Typed value before the change                                            |
| `newValue`      | any    | Typed value after the change                                             |
| `formName`      | string | Form reference for `formLoaded:*` events                                 |
| `triggerName`   | string | Trigger name for `trigger:*` events                                      |

Example:

```javascript theme={null}
if (event.attributeTag === "amount") {
  log.info("Amount changed from", event.oldValue, "to", event.newValue);
}
```

## `currentObject`

`currentObject` is the data object instance for the group data element where the event matched.

### Reading Values

| Method                         | Returns                                              | Use                                                |
| ------------------------------ | ---------------------------------------------------- | -------------------------------------------------- |
| `getPath()`                    | string                                               | Data object definition path                        |
| `getIDString()`                | string                                               | Data object ID as a string                         |
| `getParent()`                  | DataObject or `null`                                 | Parent data object                                 |
| `getChildren()`                | DataObject\[]                                        | Child objects already attached to this object      |
| `getChildrenByPath(path)`      | DataObject\[]                                        | Direct children matching a path                    |
| `getAttributeByName(name)`     | DataAttribute or `null`                              | First matching attribute wrapper                   |
| `getAttributesByName(name)`    | DataAttribute\[]                                     | All matching attribute wrappers                    |
| `getFirstAttributeValue(name)` | string, number, boolean, string date, or `undefined` | First typed attribute value                        |
| `getAttributeValues(name)`     | string\[]                                            | All matching values stringified                    |
| `hasTaxonomy()`                | boolean                                              | Whether the object has a Data Definition reference |

```javascript theme={null}
if (!currentObject) return;

var city = currentObject.getFirstAttributeValue("shipper_city");
var state = currentObject.getFirstAttributeValue("shipper_state");
var path = currentObject.getPath();

log.debug("Changed object", path, city, state);
```

### Writing Values

Use `setAttribute(name, value)` for normal writes. It finds or creates the attribute, resolves the target type from the Data Definition when available, writes the typed value, persists the change, and notifies the recalculation system.

```javascript theme={null}
var quantity = currentObject.getFirstAttributeValue("quantity") || 0;
var price = currentObject.getFirstAttributeValue("unit_price") || 0;

currentObject.setAttribute("line_total", quantity * price);
```

Supported value types are strings, numbers, booleans, and RFC 3339 date strings for date attributes.

<Warning>
  Type mismatches fail the script. For example, writing `"abc"` to a numeric data element raises a JavaScript error from the typed setter.
</Warning>

### Payloads For Service Bridges

`payload(mapping)` builds a plain JavaScript object from attributes on `currentObject`.

```javascript theme={null}
var body = currentObject.payload({
  shipperCode: "shipper_code",
  shipperCity: "shipper_city",
  shipperState: "shipper_state"
});

// { shipperCode: "ACME", shipperCity: "Chicago", shipperState: "IL" }
```

Missing attributes are returned as empty strings. This keeps Service Bridge request bodies stable.

### Creating Child Data Objects

Use `getOrCreateChild(path, opts?)` or `addChild(opts)` when the event should create modeled child data.

```javascript theme={null}
var accessorial = currentObject.getOrCreateChild("shipment/accessorials", {
  sourceOrdering: "0"
});

accessorial.setAttribute("code", "LIFTGATE");
accessorial.setAttribute("amount", 75);
```

Use `copyAttributesFrom(sourceObj, mappings, ownerUri?)` when moving known attributes between objects:

```javascript theme={null}
target.copyAttributesFrom(source, [
  { src: "invoice_number" },
  { src: "currency", dst: "amount_currency" }
]);
```

## `DataAttribute`

`getAttributeByName()` and `getAttributesByName()` return attribute wrappers for lower-level operations.

| Method                   | Use                                           |
| ------------------------ | --------------------------------------------- |
| `getTag()`               | Attribute tag/name                            |
| `getPath()`              | Full attribute path                           |
| `getOwnerUri()`          | Audit owner URI                               |
| `getValue()`             | Raw value                                     |
| `getStringValue()`       | Typed string value                            |
| `getDecimalValue()`      | Typed numeric value                           |
| `getBooleanValue()`      | Typed boolean value                           |
| `getDateValue()`         | Typed date value as RFC 3339                  |
| `getConfidence()`        | Confidence value, or `0` when absent          |
| `getDataFeatures()`      | Attribute data features                       |
| `setStringValue(value)`  | Persist typed string value                    |
| `setDecimalValue(value)` | Persist typed numeric value                   |
| `setBooleanValue(value)` | Persist typed boolean value                   |
| `setDateValue(value)`    | Persist typed date value from RFC 3339 string |
| `setConfidence(value)`   | Persist confidence                            |
| `addFeature(key, value)` | Add a data feature                            |

Prefer `currentObject.setAttribute(...)` for common writes. Use `DataAttribute` methods only when you need direct access to a specific attribute instance.

## `document`

The `document` global is the active KDDB document.

### Data Object Access

| Method                               | Use                                    |
| ------------------------------------ | -------------------------------------- |
| `getAllDataObjects()`                | Return all data objects                |
| `findDataObjectsByPath(path)`        | Return all objects for a path          |
| `findFirstDataObjectByPath(path)`    | Return the first object for a path     |
| `getOrCreate(path, opts?)`           | Find or create a top-level data object |
| `createDataObject(opts)`             | Create a data object                   |
| `deleteDataObject(id)`               | Delete a data object                   |
| `copyDataObject(opts)`               | Copy a data object                     |
| `moveDataObject(opts)`               | Move a data object                     |
| `batchCopyDataObjects(opts[])`       | Copy several objects atomically        |
| `batchMoveDataObjects(opts[])`       | Move several objects atomically        |
| `updateDataObject(obj)`              | Persist in-memory object changes       |
| `getDataObjectsByParentId(parentId)` | Return direct children for a parent ID |

### Document Metadata And External Data

| Method                                                   | Use                                   |
| -------------------------------------------------------- | ------------------------------------- |
| `getUUID()` / `getVersion()`                             | Document identity                     |
| `getLabels()` / `addLabel(label)` / `removeLabel(label)` | Document labels                       |
| `getMetadata()` / `setMetadata(key, value)`              | Document metadata map                 |
| `getDocumentMetadataMap()`                               | Copy of document metadata             |
| `getDocumentMetadataValue(key)`                          | Single document metadata value        |
| `setDocumentMetadataValue(key, value)`                   | Set or delete document metadata value |
| `getExternalData(key)`                                   | External data entry                   |
| `setExternalData(key, data)`                             | Write external data                   |
| `getExternalDataKeys()`                                  | External data keys                    |

### Content, Search, And Serialization

| Method                               | Use                                          |
| ------------------------------------ | -------------------------------------------- |
| `getRootNode()` / `getContentNode()` | Access the document content tree             |
| `select(selector, variables)`        | Select content nodes                         |
| `selectFirst(selector, variables)`   | Select the first matching node               |
| `getAllTags()`                       | Unique document tags                         |
| `searchContent(pattern, opts)`       | Regex content search                         |
| `searchLines(query, opts)`           | Line search                                  |
| `getSearchableLines()`               | Searchable line records                      |
| `toJSON()`                           | Serialize document metadata and data objects |
| `dataObjectsToJSON()`                | Serialize data objects only                  |

### Exceptions And Validations

| Method                                      | Use                          |
| ------------------------------------------- | ---------------------------- |
| `createDataException(opts)`                 | Create a data exception      |
| `updateDataException(opts)`                 | Update a data exception      |
| `closeDataException(id, comment)`           | Close a data exception       |
| `reopenDataException(id)`                   | Reopen a data exception      |
| `getAllDataExceptions()`                    | All data exceptions          |
| `getOpenDataExceptions()`                   | Open data exceptions         |
| `getDataExceptionById(id)`                  | One data exception           |
| `getDataExceptionsByDataObjectId(id)`       | Exceptions for a data object |
| `addValidation(opts)`                       | Add validation metadata      |
| `getValidations()` / `setValidations(opts)` | Read or replace validations  |
| `validateDocument()`                        | Run document validation      |

Example:

```javascript theme={null}
var amount = currentObject.getFirstAttributeValue("amount");

if (!amount || amount <= 0) {
  document.createDataException({
    message: "Amount must be greater than zero",
    exceptionType: "validation",
    severity: "error",
    dataObjectId: event.dataObjectId
  });
}
```

## `serviceBridge`

`serviceBridge.call(bridgeRef, endpointName, body?)` calls an external system through a configured Service Bridge.

```javascript theme={null}
var result = serviceBridge.call(
  "acme-finance/vendor-lookup",
  "match-vendor",
  currentObject.payload({
    vendorName: "vendor_name",
    postalCode: "postal_code"
  })
);

if (result && result.vendorId) {
  currentObject.setAttribute("vendor_id", result.vendorId);
  currentObject.setAttribute("vendor_match_status", "matched");
}
```

Bridge refs may be fully qualified as `orgSlug/bridgeSlug`. Some runtimes can supply a default organization slug, but fully qualifying the bridge keeps scripts portable.

The bridge response is parsed as JSON. If the platform proxy returns a `result` envelope, the runtime returns the `result` value directly. If the proxy returns an `error` envelope, the script fails with that error.

## `taxon`

In the browser subscription runtime, `taxon.optionLabel(taxonName, value)` returns the display label for a selection option value.

```javascript theme={null}
var movementType = currentObject.getFirstAttributeValue("movement_type");
var label = taxon.optionLabel("movement_type", movementType);

if (label) {
  currentObject.setAttribute("movement_type_label", label);
}
```

Guard this helper if the same script may run in a runtime that has not loaded Data Definition bindings:

```javascript theme={null}
if (typeof taxon !== "undefined") {
  var label = taxon.optionLabel("movement_type", value);
}
```

## `bridge`

The browser subscription runtime exposes `bridge` for UI notifications and explicit event emission.

| Method                                           | Use                                                    |
| ------------------------------------------------ | ------------------------------------------------------ |
| `bridge.notify(title, message, severity?)`       | Show a user notification. Severity defaults to `info`. |
| `bridge.events.fire(eventString, dataObjectId?)` | Emit another event into the recalculation cascade.     |

```javascript theme={null}
if (typeof bridge !== "undefined") {
  bridge.notify("Vendor matched", "Vendor ID was populated.", "success");
}
```

Fire a follow-up event when another subscription should respond:

```javascript theme={null}
if (typeof bridge !== "undefined") {
  bridge.events.fire("trigger:recalculate-rating", event.dataObjectId);
}
```

<Warning>
  Only emit events deliberately. Emitted events participate in the same loop-control rules as automatic events.
</Warning>

## Logging

Use structured logging during development and troubleshooting:

```javascript theme={null}
log.debug("event", event.type, "object", currentObject.getPath());
log.info("vendor response", JSON.stringify(result));
log.warn("missing postal code");
log.error("unexpected response shape", JSON.stringify(result));
```

`console.log`, `console.warn`, and `console.error` are also available.

## Loop Control

The recalculation service protects event scripts from runaway cascades:

| Guard                 | Behavior                                                                              |
| --------------------- | ------------------------------------------------------------------------------------- |
| Active frame guard    | A subscription cannot re-enter itself while already running for the same data object. |
| Cascade deduplication | A subscription runs at most once per data object in one user-driven cascade.          |
| Maximum depth         | Nested event execution is capped at 8 levels.                                         |
| No-op suppression     | Writes that do not change typed values are suppressed.                                |

Design scripts to be idempotent. A script may run more than once over the life of a document, and retries should not duplicate data or re-open resolved work.

## Examples

### Normalize And Derive Sibling Values

```yaml theme={null}
eventSubscriptions:
  - name: normalize-postal-code
    on: "changed:dataAttribute:postal_code"
    script: |
      if (!currentObject) return;

      var postalCode = currentObject.getFirstAttributeValue("postal_code");
      if (!postalCode) return;

      currentObject.setAttribute("postal_code", String(postalCode).trim().toUpperCase());
      currentObject.setAttribute("postal_code_prefix", String(postalCode).slice(0, 5));
```

### Service Bridge Enrichment

```yaml theme={null}
eventSubscriptions:
  - name: lookup-vendor
    on: "changed:dataAttribute:(vendor_name|postal_code)"
    script: |
      if (!currentObject) return;

      var vendorName = currentObject.getFirstAttributeValue("vendor_name");
      if (!vendorName) return;

      var result = serviceBridge.call(
        "acme-finance/vendor-lookup",
        "match-vendor",
        currentObject.payload({
          vendorName: "vendor_name",
          postalCode: "postal_code"
        })
      );

      if (result && result.vendorId) {
        currentObject.setAttribute("vendor_id", result.vendorId);
        currentObject.setAttribute("vendor_match_status", "matched");
      } else {
        currentObject.setAttribute("vendor_match_status", "review");
      }
```

### Create A Data Exception

```yaml theme={null}
eventSubscriptions:
  - name: validate-amount
    on: "changed:dataAttribute:amount"
    script: |
      if (!currentObject) return;

      var amount = currentObject.getFirstAttributeValue("amount");
      if (amount === undefined || amount === null || amount <= 0) {
        document.createDataException({
          message: "Amount must be greater than zero",
          exceptionType: "validation",
          severity: "error",
          dataObjectId: event.dataObjectId
        });
      }
```

### Emit A Follow-Up Event

```yaml theme={null}
eventSubscriptions:
  - name: vendor-change
    on: "changed:dataAttribute:vendor_id"
    script: |
      if (typeof bridge !== "undefined") {
        bridge.events.fire("trigger:recalculate-risk", event.dataObjectId);
      }

  - name: recalculate-risk
    on: "trigger:recalculate-risk"
    script: |
      if (!currentObject) return;
      var amount = currentObject.getFirstAttributeValue("amount") || 0;
      var vendorStatus = currentObject.getFirstAttributeValue("vendor_status");
      currentObject.setAttribute(
        "risk_level",
        amount > 10000 || vendorStatus === "new" ? "review" : "standard"
      );
```

## Constraints

<Warning>
  Event subscriptions can only be declared on group data elements.
</Warning>

<Warning>
  Scripts run synchronously with a 2-second timeout. Keep external calls bounded and avoid large document scans.
</Warning>

<Warning>
  Subscription scripts are scoped to the active document. Use the prebound `document` global; do not call `loadDocument()`.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Start with the event string" icon="bolt">
    Name the exact business event the script responds to. Prefer specific `on` patterns such as `changed:dataAttribute:(quantity|unit_price)` over broad matches.
  </Accordion>

  <Accordion title="Keep the script local to the group" icon="diagram-project">
    Read and write values on `currentObject` when possible. Use `document` only when the script really needs broader document context.
  </Accordion>

  <Accordion title="Make writes idempotent" icon="rotate">
    Write the same result for the same inputs. Avoid appending duplicate child objects or recreating exceptions without checking existing state.
  </Accordion>

  <Accordion title="Use typed values" icon="sliders">
    `getFirstAttributeValue` returns typed values. Preserve those types when writing with `setAttribute`.
  </Accordion>

  <Accordion title="Guard browser-only helpers" icon="code">
    Wrap `bridge` and the `taxon` helper usage in `typeof ... !== "undefined"` checks when a script may also run outside the browser runtime.
  </Accordion>

  <Accordion title="Log the decision, not every value" icon="terminal">
    Use `log.debug` while building and keep production logs focused on decisions, external calls, and unexpected states.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Data Definitions" icon="sitemap" href="/guides/data-definitions/index">
    Define the data model that event scripts operate on
  </Card>

  <Card title="Data Definition Structure" icon="book-open" href="/guides/data-definitions/taxonomy-guide">
    Configure data elements, groups, validations, selection options, and event subscriptions
  </Card>

  <Card title="Selection Option Formulas" icon="list-check" href="/guides/data-definitions/selection-option-formulas">
    Compute dynamic selection options from data and bridge calls
  </Card>

  <Card title="Service Bridges" icon="plug" href="/guides/scripting/service-bridges">
    Configure external API calls used by event scripts
  </Card>
</CardGroup>
