Skip to main content
Script steps allow you to embed JavaScript logic directly into a plan workflow. Use them for routing decisions, document inspection, data extraction, content tagging, knowledge feature assignment, and any custom processing that needs to run between other plan steps.

Overview

A script step runs inline JavaScript in a sandboxed runtime. It has access to:
  • The current task and its properties
  • Document families attached to the task
  • The ability to load KDDB documents and inspect or modify their content
  • Knowledge feature lookups for classification workflows
  • Logging for diagnostics
Scripts return an action that determines which downstream path the workflow follows, enabling conditional branching in your plan.

Adding a Script Step

1

Enable Planned Sub-Tasks

In the task template editor, check Enable Planned Sub-Tasks on the Details tab.
2

Open the Planned Tab

Navigate to the Planned tab to open the visual flow editor.
3

Add a Script Node

Drag a Script node from the left palette onto the canvas. Script nodes appear in violet.
4

Write Your Script

Select the script node and use the properties panel on the right to write JavaScript. Click the Expand button to open the full-screen editor with the snippet browser.
5

Define Actions

Add one or more actions in the Script Actions section. These are the possible outcomes your script can return. Each action becomes a connection point on the node for conditional branching.
6

Connect Dependencies

Draw edges from upstream nodes to your script node, and from the script node’s action handles to downstream nodes.

Script Structure

Every script must return an object with an action property that matches one of the declared action names:
// Inspect task data, documents, or knowledge features
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true);

// Make a routing decision
var isInvoice = content.indexOf("INVOICE") !== -1;

doc.close();

// Return the action to follow
return { action: isInvoice ? "invoice" : "other" };
Optionally, the return value can include a features array to assign knowledge features to document families:
return {
  action: "classified",
  features: [
    { documentFamilyId: families[0].id, featureId: "feature-uuid" }
  ]
};

Context Objects

Scripts have access to several pre-bound context objects. These are available as global variables in your script.

task

The current task being processed.
PropertyTypeDescription
idstringTask ID
titlestringTask title
statusstringCurrent task status slug
metadataobjectTask metadata (from the template)
dataobjectTask properties/data (custom key-value pairs)

families

An array of document families attached to the task.
PropertyTypeDescription
idstringDocument family ID
namestringFile path / name
metadataobjectFamily metadata
mixinsstring[]Mixin slugs applied to this family
featureIdsstring[]Knowledge feature IDs assigned to this family
contentObjectsarrayContent objects (documents and native files)
Each content object has:
PropertyTypeDescription
idstringContent object ID
contentTypenumber0 = KDDB document, 1 = native file
mimeTypestringMIME type (e.g., application/pdf)
sizenumberFile size in bytes

org

The current organization.
PropertyTypeDescription
idstringOrganization ID
slugstringOrganization slug

Helper Functions

log(level, message)

Write a log entry. Level can be "debug", "info", "warn", or "error".
log("info", "Processing document: " + families[0].name);
log("warn", "No content found in document");

loadDocument(familyId)

Load a KDDB document by its family ID. Returns a document object with methods for reading and modifying content. Limited to 5 calls per script execution.
var doc = loadDocument(families[0].id);
// ... inspect or modify the document ...
doc.close();
Always call doc.close() when you are finished with a document. This frees resources and, if you modified the document, persists the changes as a new content object version.

lookupFeature(slug)

Look up a single knowledge feature by its slug. Returns the feature object or null.
var feature = lookupFeature("doc-type-invoice");
if (feature) {
  log("info", "Found feature: " + feature.id);
}

lookupFeatureType(slug)

Look up a knowledge feature type by its slug. Returns { id, name, slug } or null.
var featureType = lookupFeatureType("document-classification");
if (featureType) {
  log("info", "Feature type: " + featureType.name);
}

lookupFeaturesByType(typeSlug)

Get all knowledge features belonging to a feature type. Returns an array of { id, slug } objects.
var features = lookupFeaturesByType("document-classification");
for (var i = 0; i < features.length; i++) {
  log("info", "Feature: " + features[i].slug);
}

Working with Documents

When you call loadDocument(), you receive a document object with both read and write capabilities.

Reading Document Content

var doc = loadDocument(families[0].id);

// Get the root content node
var root = doc.getRootNode();

// Get all text content joined with spaces
var fullText = root.getAllContent(" ", true);

// Get document metadata
var metadata = doc.getMetadata();
log("info", "Document type: " + metadata.documentType);

// Get document labels
var labels = doc.getLabels();
log("info", "Labels: " + labels.join(", "));

doc.close();

Selecting Content Nodes

Use selector expressions to find specific content nodes in the document tree:
var doc = loadDocument(families[0].id);

// Find all page nodes
var pages = doc.select("//page");
log("info", "Document has " + pages.length + " pages");

// Find lines containing specific text
var matches = doc.select("//line[contains(@content, 'TOTAL')]");

// Find the first matching node
var header = doc.selectFirst("//line[position() = 1]");
if (header) {
  log("info", "First line: " + header.getContent());
}

doc.close();
Content nodes form a tree structure (document > page > line > word). You can navigate it programmatically:
var doc = loadDocument(families[0].id);
var root = doc.getRootNode();

// Get child nodes
var pages = root.getChildren();
for (var i = 0; i < pages.length; i++) {
  var page = pages[i];
  log("info", "Page " + page.getPage() + ": " + page.getNodeType());

  // Get lines on this page
  var lines = page.getChildren();
  for (var j = 0; j < lines.length; j++) {
    log("debug", "  Line: " + lines[j].getContent());
  }
}

doc.close();

Content Node Properties

Each content node provides:
MethodReturnsDescription
getContent()stringText content of this node
getNodeType()stringNode type (page, line, word, etc.)
getChildren()node[]Child content nodes
getParent()nodeParent content node
getDescendants()node[]All descendant nodes
getAllContent(sep, strip)stringJoin all descendant text
getPage()numberPage number
getBoundingBox()object{ x, y, width, height } or null
getConfidence()numberOCR confidence score
getTags()arrayTags on this node
hasTag(name)booleanCheck for a specific tag
getFeatures()arrayFeatures on this node
hasFeature(type, name)booleanCheck for a specific feature
getFeatureValue(type, name)anyGet a feature value

Modifying Documents

Script steps can modify documents. Changes are automatically persisted as a new content object version when you call doc.close().

Setting Metadata and Labels

var doc = loadDocument(families[0].id);

// Set metadata values
doc.setMetadata("processedAt", new Date().toISOString());
doc.setMetadata("documentType", "invoice");

// Add and remove labels
doc.addLabel("reviewed");
doc.removeLabel("pending");

doc.close();

Tagging Content Nodes

Tags mark content nodes for downstream processing or data extraction:
var doc = loadDocument(families[0].id);

// Find and tag specific content
var nodes = doc.select("//line[contains(@content, 'TOTAL')]");
for (var i = 0; i < nodes.length; i++) {
  nodes[i].tag("financial/total-line", {
    confidence: 0.95,
    value: nodes[i].getContent()
  });
}

doc.close();
Tag options:
OptionTypeDescription
valuestringTag value
confidencenumberConfidence score (0.0–1.0)
groupUuidstringGroup UUID for data object grouping
parentGroupUuidstringParent group UUID for hierarchy
cellIndexnumberInstance index within a group
indexnumberOrdering index
notestringUser note
statusstringTag status
ownerUristringProvenance URI

Adding Features to Content Nodes

var doc = loadDocument(families[0].id);

var pages = doc.select("//page");
for (var i = 0; i < pages.length; i++) {
  var page = pages[i];
  var content = page.getAllContent(" ", true);

  // Classify page type based on content
  var pageType = "body";
  if (content.indexOf("Table of Contents") !== -1) {
    pageType = "toc";
  } else if (content.match(/appendix\s+[a-z]/i)) {
    pageType = "appendix";
  }

  page.setFeature("classification", "page_type", pageType);
}

doc.close();

Creating Data Objects

Data objects represent structured, extracted data:
var doc = loadDocument(families[0].id);

// Create a top-level data object
var invoice = doc.createDataObject({
  path: "invoice",
  taxonomyRef: "taxonomy://acme/invoice-taxonomy"
});

// Add attributes
invoice.addAttribute({
  tag: "vendor_name",
  path: "invoice/vendor_name",
  value: "Acme Corp",
  type: "STRING"
});

invoice.addAttribute({
  tag: "total_amount",
  path: "invoice/total_amount",
  value: "$1,234.56",
  type: "CURRENCY",
  decimalValue: 1234.56
});

doc.close();

Nested Data Objects

Build hierarchical data structures with parent/child relationships:
var doc = loadDocument(families[0].id);

var order = doc.createDataObject({
  path: "purchase_order",
  taxonomyRef: "taxonomy://acme/po-taxonomy"
});

order.addAttribute({
  tag: "po_number",
  path: "purchase_order/po_number",
  value: "PO-2026-0042",
  type: "STRING"
});

var items = [
  { sku: "WDG-100", qty: 50, price: 12.99 },
  { sku: "WDG-200", qty: 25, price: 24.50 }
];

for (var i = 0; i < items.length; i++) {
  var lineItem = order.addChild({
    path: "purchase_order/line_item",
    sourceOrdering: String(i)
  });

  lineItem.addAttribute({
    tag: "sku",
    path: "purchase_order/line_item/sku",
    value: items[i].sku,
    type: "STRING"
  });

  lineItem.addAttribute({
    tag: "unit_price",
    path: "purchase_order/line_item/unit_price",
    value: "$" + items[i].price.toFixed(2),
    type: "CURRENCY",
    decimalValue: items[i].price
  });
}

doc.close();

Reading Data Objects

var doc = loadDocument(families[0].id);

// Get all data objects
var allObjects = doc.getAllDataObjects();
log("info", "Total data objects: " + allObjects.length);

// Find objects by path
for (var i = 0; i < allObjects.length; i++) {
  var obj = allObjects[i];
  if (obj.getPath() === "invoice") {
    // List attributes
    var attrs = obj.getAttributes();
    for (var j = 0; j < attrs.length; j++) {
      log("info", "  " + attrs[j].getValue());
    }

    // Find attribute by name
    var amount = obj.getAttributeByName("total_amount");
    if (amount) {
      log("info", "Total: " + amount.getValue());
    }

    // Find children by path
    var lineItems = obj.getChildrenByPath("invoice/line_item");
    log("info", "Line items: " + lineItems.length);
  }
}

doc.close();

Attribute Data Types

When adding attributes, specify the type to control how the value is stored and displayed:
TypeDescriptionTyped Value Property
STRINGFree textstringValue
CURRENCYMonetary amountdecimalValue
NUMBERNumeric valuedecimalValue
PERCENTAGEPercentagedecimalValue
DECIMALDecimal numberdecimalValue
INTEGERWhole numberdecimalValue
DATEDatedateValue (ISO string)
DATETIMEDate and timedateValue (ISO string)
BOOLEANTrue/falsebooleanValue
SELECTIONEnumerated valuestringValue
URLWeb addressstringValue
EMAILEmail addressstringValue
PHONEPhone numberstringValue

Knowledge Feature Assignment

Scripts can assign knowledge features to document families as part of their return value. This is useful for classification workflows where the script analyzes document content and assigns the appropriate features.
var doc = loadDocument(families[0].id);
var content = doc.getRootNode().getAllContent(" ", true).toLowerCase();
var features = [];

// Check for document characteristics
if (content.indexOf("invoice") !== -1) {
  var feature = lookupFeature("doc-type-invoice");
  if (feature) {
    features.push({
      documentFamilyId: families[0].id,
      featureId: feature.id
    });
  }
}

// Add a label for tracking
if (features.length > 0) {
  doc.addLabel("classified");
  doc.setMetadata("classifiedAt", new Date().toISOString());
}

doc.close();

return {
  action: features.length > 0 ? "classified" : "unclassified",
  features: features
};

Full Workflow Example

This example combines document inspection, tagging, data extraction, and knowledge feature assignment into a single script:
var doc = loadDocument(families[0].id);
var features = [];

// 1. Classify document type
var root = doc.getRootNode();
var fullText = root.getAllContent(" ", true);
var isInvoice = fullText.indexOf("INVOICE") !== -1;

// 2. Set metadata and labels
doc.setMetadata("documentType", isInvoice ? "invoice" : "unknown");
doc.addLabel(isInvoice ? "invoice" : "unclassified");

// 3. Assign knowledge feature
if (isInvoice) {
  var feat = lookupFeature("doc-type-invoice");
  if (feat) {
    features.push({
      documentFamilyId: families[0].id,
      featureId: feat.id
    });
  }
}

// 4. Tag header lines
var headerNodes = doc.select("//line[position() <= 5]");
for (var i = 0; i < headerNodes.length; i++) {
  headerNodes[i].tag("document/header-line");
  headerNodes[i].setFeature("layout", "section", "header");
}

// 5. Extract data if invoice
if (isInvoice) {
  var invoice = doc.createDataObject({
    path: "invoice",
    taxonomyRef: "taxonomy://acme/invoice"
  });

  var invNumNode = doc.selectFirst(
    "//line[contains(@content, 'Invoice #')]"
  );
  if (invNumNode) {
    var text = invNumNode.getContent();
    var match = text.match(/Invoice #\s*(\S+)/);
    if (match) {
      invoice.addAttribute({
        tag: "invoice_number",
        path: "invoice/invoice_number",
        value: match[1],
        type: "STRING",
        confidence: 0.85
      });
    }
  }
}

doc.close();

return {
  action: isInvoice ? "invoice-processed" : "skipped",
  features: features
};

The Script Editor

The plan flow editor provides two ways to edit script code:

Inline Editor

When you select a script node, the properties panel on the right shows a Monaco code editor with:
  • Syntax highlighting for JavaScript
  • IntelliSense auto-completion for all context objects, helper functions, and document APIs
  • Script Actions management below the editor

Full-Screen Editor

Click the Expand button to open the full-screen script editor, which provides:
  • A large code editing area
  • A Snippet Browser panel on the right with categorized code templates
  • Insert or Replace buttons to use snippets as starting points
  • Script action management in the footer

Snippet Categories

The snippet browser includes ready-to-use templates organized by category:
CategoryWhat’s Included
Getting StartedBasic script skeleton with document loading
Tagging NodesTag nodes by selector or by ID list
Data ObjectsCreate, nest, and look up data objects and attributes
Knowledge FeaturesClassify documents and assign features
Content AnalysisPage classification by content heuristics
Full WorkflowsEnd-to-end examples combining all capabilities

Execution Details

  • Runtime: JavaScript (ES5+) via the Goja engine
  • Timeout: 15 seconds per script execution
  • Document loads: Maximum 5 per script execution
  • Persistence: Any document modifications (tags, metadata, labels, data objects) are saved as new content object versions when close() is called
  • Action matching: The returned action name is matched case-insensitively against the declared script actions