Show only results for:












Custom Code and Macro Reference

This document provides a complete reference for the ExecutorCustomCode macro system, including workflow step macros, event listeners, and field validators. It covers all available capabilities, how to use them, and their current implementation status.

Related Administration Pages:


1. Macro Requirements

A. One Macro per Workflow Step

Each workflow step can have a Custom Code block that executes when the step is accepted or denied.

How to configure:

  1. Navigate to Settings > Workflows
  2. Select a workflow from the list
  3. Click the Code cell for the desired workflow step
  4. Write JavaScript code in the integrated code editor
  5. Click Compile to verify syntax, then Save

How it works:

  • The macro runs when the workflow step is triggered (accepted or denied)
  • The userAction.Result object tells you whether the step was accepted (ActionResult.Yes) or denied (ActionResult.No)
  • Each step can have exactly one code block

Example:

if (userAction.Result === ActionResult.Yes) {
  await setStatus("Approved");
  await sendMailTo(
    ["manager@example.com"],
    "Step approved for Ticket #" + (await getTicketValue("PunchItem")),
    "Workflow Progress"
  );
} else {
  await stepBackwardJumpTo(1, "Returned to initial review");
}

Note: In addition to workflow step macros, you can use Event Listeners for event-driven automation that is not tied to a specific step action.


B. Workflow-Level Event Macros

Event listeners enable macros that trigger on specific events across the application. Unlike step macros, event listeners react to conditions such as field changes, email receipts, and scheduled intervals.

How to configure:

  1. Navigate to Settings > Listeners
  2. Click the Add (+) button
  3. Select a Type from the dropdown
  4. Configure the required fields for that type
  5. Write Custom Code and save

Available Event Types

Event Type Trigger Required Fields
onWorkflowStepAccepted A workflow step is accepted Checked Field (step number), Checked Value, Workflow ID
onValueChanged A ticket field value matches a condition Checked Field (field name), Checked Value
onTicketCreated A new ticket is created Default (typically true)
onInteractionEmailSent An interaction email is sent Subject Regex, Content Regex
onTicketOverdue Scheduled check for overdue tickets Interval (cron), Overdue (ms)
onEmailReceived An inbound email is received Subject Regex, Content Regex, Interval (cron)

ValueChangeValidating

Field validators provide a pre-save validation event. When a ticket field value changes, all active validators for that field run before the value is saved.

How to configure:

  1. Navigate to Settings > Workflows > Validators
  2. Select a workflow from the dropdown
  3. Click Add to create a new validator
  4. Select one or more fields from the Fields TagBox
  5. Enter an Error Message (shown to the user on failure)
  6. Write validation code that returns true (allow) or false (block)

See Field Validators for full details.

ValueChanged (onValueChanged)

Triggers after a ticket field value changes to a specified value. Unlike validators, listeners cannot block the change.

Configuration example:

Type: onValueChanged
Checked Field: Severity
Checked Value: Critical
Custom Code:
  await stepForwardJumpTo(5, "Critical severity detected - escalating");

EmailReceived

Triggers when an inbound email matches regex patterns on subject and/or body content.

Configuration example:

Type: onEmailReceived
Subject Regex: (Out of Office|Automatic reply)
Content Regex: .*
Interval: */5 * * * *
Custom Code:
  console.log("Auto-reply detected");

2. Required Capabilities

A. Detecting Received Emails

The system can detect inbound emails with specific content using regex pattern matching.

How to configure:

  1. Navigate to Settings > Listeners
  2. Add a listener with Type = onEmailReceived
  3. Set Subject Regex (regex pattern to match the email subject)
  4. Set Content Regex (regex pattern to match the email body)
  5. Set Interval (cron expression for how often to poll, e.g., */5 * * * * for every 5 minutes)
  6. Write Custom Code to execute when a matching email is found

Use cases:

  • Detect autoresponder / out-of-office replies: Subject Regex: (?i)(Out of Office|Automatic reply|Abwesenheit)
  • Detect approval keywords in email body: Content Regex: (?i)(approved|confirmed)
  • Trigger ticket creation from inbound emails

How it works internally:

  • A cron job polls for new emails via the Microsoft Graph API at the configured interval
  • Each email is matched against the listener’s SubjectRegex and ContentRegex
  • The ProcessedEmailListeners database table prevents duplicate processing
  • When matched, the listener’s Custom Code executes with full access to the macro API

B. Detecting Ticket Value Changes

Two systems detect ticket value changes:

System 1: Event Listeners (post-change)

Listeners of type onValueChanged check if a field has a specific value after a change.

Configuration:

Field Value
Type onValueChanged
Checked Field Field name (e.g., Severity)
Checked Value Target value (e.g., Critical)
Workflow ID Target workflow (or set Default = true for all)

Example: Route to different workflow based on severity

// Listener: onValueChanged, CheckedField: "Severity", CheckedValue: "Critical"
await stepForwardJumpTo(5, "Escalated due to critical severity");
await sendMailTo(
  ["escalation-team@example.com"],
  "Ticket has been escalated to critical severity.",
  "Critical Escalation"
);

System 2: Field Validators (pre-change)

Validators run before the value is saved and can block the change.

Example: Different validation based on severity

// Validator on "Severity" field
// ErrorMessage: "Only managers can set severity to Critical"
if (getCurrentFieldValue() === "Critical") {
  return hasRole("Manager");
}
return true;

C. User Rights

Custom code can access the current user’s roles to enforce permission-based logic.

In Field Validators

The hasRole(roleName) function is available and checks whether the current user has a specific role.

Example:

// Only users with "Editor" role can modify this field
if (!hasRole("Editor")) {
  return false; // ErrorMessage: "You don't have the right to modify this value."
}
return true;

Available helper:

Function Description
hasRole(roleName) Returns true if the current user has the named role, false otherwise

Available variable:

Variable Description
userRoles Array of the current user’s role names (e.g., ["Admin", "Editor"])

In Workflow Step Macros and Event Listeners

User roles are not directly available in workflow macros. The executor receives userID but roles are not pre-loaded into the execution context.


3. Actions Within a Macro

Trigger a Jump to a Certain Workflow Step

Custom code can override the default forward/backward step configured in the workflow table.

stepForwardJumpTo(stepNumber, message?)

Jumps to the specified step number as a success/approval action. Overrides the Forward Step ID column.

// Skip to step 5 with an activity log message
await stepForwardJumpTo(5, "Auto-approved based on priority");

stepBackwardJumpTo(stepNumber, message?)

Jumps to the specified step number as a failure/denial action. Overrides the Backward Step ID column.

// Send back to step 1 for rework
await stepBackwardJumpTo(1, "Additional information required");

Note: When these functions are used, they take priority over the Forward Step ID and Backward Step ID values defined in the workflow table.


Refuse to Modify a Value

Field validators can prevent a value from being saved by returning false.

How it works:

  1. User changes a field value
  2. All active validators for that field execute in order
  3. If any validator returns false, the change is blocked
  4. The configured Error Message is shown as a toast notification
  5. The field retains its previous value

Example: Prevent unauthorized changes

// Validator on "Budget" field
// ErrorMessage: "You don't have the right to modify this value."
if (!hasRole("Finance")) {
  return false;
}
return true;

Example: Conditional refusal

// Validator on "Status" field
// ErrorMessage: "Cannot close ticket without a resolution."
if (getCurrentFieldValue() === "Closed") {
  const resolution = getFieldValue("Resolution");
  return resolution && resolution.trim() !== "";
}
return true;

4. Misc Functions

A. Show Message

In Field Validators

When a validator returns false, the Error Message configured on the validator is displayed to the user as a toast notification. This is the primary mechanism for showing messages during field validation.

How to configure:

  1. Navigate to Settings > Workflows > Validators
  2. Create or edit a validator
  3. Set the Error Message field (max 500 characters)
  4. Write validation code that returns false when the message should be shown

Example:

Error Message: "You don't have the right to modify this value."
Custom Code: return hasRole("Editor");

In Workflow Step Macros

There is currently no showMessage() function available in workflow macros. Console output via console.log() is captured during compilation/testing but is not displayed to the user in production mode.


B. Sending Emails

The sendMailTo() function sends emails via the Microsoft Graph API.

Function Signature

await sendMailTo(recipients, message, subject, attachments?)
Parameter Type Description
recipients string[] Array of email addresses
message string HTML email body (supports template variables)
subject string Email subject line (supports template variables)
attachments any[] (optional) Array of attachments (PDF reports, UPVF files)

Basic Usage

await sendMailTo(
  ["d.dolinsky@caxperts.com", "daniel.dolinsky@dolinsky.de"],
  "Ticket has been approved.",
  "Ticket Approval Notification"
);

HTML Email Body

The message parameter accepts full HTML:

await sendMailTo(
  ["team@example.com"],
  `<h2>Ticket Update</h2>
   <p>Ticket <strong>#${await getTicketValue("PunchItem")}</strong> has been approved.</p>
   <ul>
     <li><strong>Status:</strong> Approved</li>
     <li><strong>Date:</strong> ${Date("MMMM dd, yyyy")}</li>
   </ul>
   <p><a href="${serverAddress}/tickets/${UniqueID}">View Ticket</a></p>`,
  "Ticket Approval Notification"
);

With PDF Attachment

const report = await createPdfReport("ReportTemplate");
await sendMailTo(
  ["user@example.com"],
  "Please see the attached report.",
  "Monthly Report",
  report
);

With UPVF File Attachment

const upvfFile = await extractUpvf("drawing.upvf");
attachments.push(upvfFile);
await sendMailTo(
  ["user@example.com"],
  "UPVF file attached.",
  "Document Delivery",
  attachments
);

With Multiple Attachments (PDF + UPVF)

attachments.push(createPdfReport("Template"));
attachments.push(extractUpvf());
await sendMailTo(
  ["user@example.com"],
  "Report and UPVF file attached.",
  "Combined Delivery",
  attachments
);

Note: The attachments array is pre-defined in the execution context. You can push items to it and pass it to sendMailTo.

Template Variables in Emails

Email message and subject support ${expression} template variable syntax. See String Replacements for the full list of supported variables.

Environment behavior: Emails are only sent when NEXT_PUBLIC_PRODUCTION=1. In development mode, emails are logged to the console instead of being sent.


C. String Replacements

The system uses ES6 template literal syntax ${expression} for dynamic variable substitution in email templates and workflow email messages.

Supported Variables

Pattern Example Output Description
${UniqueID} 123 Ticket unique ID
${PunchItem} 123 Ticket punch item ID
${Details["Field Name"]} Broken pipe Any ticket field value
${Details.Subject} Leak in area B Shorthand for field access (no spaces in name)
${serverAddress} https://toolkit.example.com Application server URL (from NEXTAUTH_URL)
${Date("format")} 2025-12-01 Current date with format string
${FormatDate(value, "format")} 01/12/2025 Format any date value
${ParseFormatDate(str, inFmt, outFmt)} 2025-12-01 Parse a date string and reformat it
${Workflow[1].Note} Manager approved Workflow step note (by step number)
${Workflows[0].Status} Accepted Workflow step status (by array index, legacy)

Date Format Patterns

Format Example
yyyy-MM-dd 2025-12-01
MM/dd/yyyy 12/01/2025
dd-MM-yyyy 01-12-2025
yyyy-MM-dd HH:mm:ss 2025-12-01 14:30:00
MMMM dd, yyyy December 01, 2025
dd.MM.yyyy 01.12.2025

Usage in Custom Code

The Date function is directly available in custom code. It supports two syntaxes:

// As a template variable (calls toString, returns yyyy-MM-dd)
const msg = `Today is ${Date}`;

// As a function call (with optional format string)
await setTicketValue("ApprovalDate", Date("yyyy-MM-dd"));
await setTicketValue("Timestamp", Date("yyyy-MM-dd HH:mm:ss"));

FormatDate and ParseFormatDate

These helper functions are available in custom code for formatting arbitrary dates:

// FormatDate(dateInput, formatString?) - format any date value
const formatted = FormatDate("2025-12-01", "dd/MM/yyyy"); // "01/12/2025"
const fromDate = FormatDate(new Date(), "MMMM dd, yyyy"); // "February 13, 2026"

// ParseFormatDate(dateString, inputFormat, outputFormat?) - parse and reformat
const result = ParseFormatDate("01-12-2025", "dd-MM-yyyy", "yyyy/MM/dd"); // "2025/12/01"

serverAddress in Custom Code

The serverAddress and UniqueID variables are directly available in custom code for building full ticket URLs.

Note: serverAddress is derived from NEXTAUTH_URL and already includes the instance path (e.g., /instance2), so you don’t need to append it separately.

// Build full ticket URL (serverAddress already includes instance path)
const ticketUrl = `${serverAddress}/tickets/${UniqueID}`;
// Example: https://toolkit-dev.udith.io/instance2/tickets/102

await sendMailTo(
  ["user@example.com"],
  `<p><a href="${ticketUrl}">View Ticket #${UniqueID}</a></p>`,
  "Ticket Link"
);

await sendMailTo(
  ["team@example.com"],
  `<h2>Ticket Update</h2>
   <p>Ticket <strong>#${UniqueID}</strong> has been approved.</p>
   <p><a href="${serverAddress}/tickets/${UniqueID}">View Ticket</a></p>`,
  "Ticket Approval Notification"
);
Function Parameters Description
Date(format?) format: date-fns format string (default: yyyy-MM-dd) Current date. Works as ${Date} or Date("format")
FormatDate(dateInput, format?) dateInput: Date, string, or timestamp; format: output format (default: yyyy-MM-dd) Format any date value. Returns "" on invalid input
ParseFormatDate(str, inFmt, outFmt?) str: date string; inFmt: input format; outFmt: output format (default: yyyy-MM-dd) Parse a date string with known format and reformat it

Usage in Email Templates

Template variables are used in Workflow Email Messages (configured in the workflow table) and in sendMailTo() message/subject parameters:

<h2>Ticket Update Notification</h2>
<p>Ticket <strong>#${PunchItem}</strong> has been updated.</p>
<ul>
  <li><strong>Area System:</strong> ${Details["Area System"]}</li>
  <li><strong>Date:</strong> ${Date("MMMM dd, yyyy")}</li>
</ul>
<p><a href="${serverAddress}/tickets/${PunchItem}">View Ticket</a></p>

Safe Proxies

Missing or undefined variables return an empty string instead of throwing an error. For example, ${NonExistentField} renders as "".


D. Ticket Read/Write (including .upvf-Blobs)

Custom code has full read and write access to ticket data and file attachments.

Reading Ticket Data

Function Description
await getTicketValue(fieldName) Returns the value of a single ticket field
await getPunchlistDetails() Returns an object with all ticket field names and values

Examples:

const tagNo = await getTicketValue("Tag No");
console.log("Tag Number:", tagNo);

const allFields = await getPunchlistDetails();
console.log("Status:", allFields["Status"]);

Writing Ticket Data

Function Description
await setTicketValue(fieldName, value) Sets a field value (creates if it doesn’t exist)
await updatePunchlistDetails(data) Batch-updates multiple fields
await createHiddenField(data) Creates a ticket-specific hidden field

Examples:

await setTicketValue("Status Comment", "Auto-approved by macro");
await setTicketValue("ApprovalDate", Date("yyyy-MM-dd"));

await updatePunchlistDetails([
  { "Tag No": "P-12345" },
  { "Unit No": "U-001" }
]);

await createHiddenField({ "InternalFlag": "processed" });

UPVF File Operations

Function Description
await extractUpvf(fileName?) Extracts a UPVF file as a Buffer object
await deleteUpvf(fileID) Soft-deletes a UPVF file (sets Hidden = true)

ExtractUpvf returns an object with:

{
  filename: "drawing.upvf",        // File name
  content: Buffer,                 // Binary content as Buffer
  contentType: "application/json", // MIME type
  fileInfo: {
    ID: 42,                        // File ID (use with DeleteUpvf)
    FileName: "drawing.upvf",
    DateTimeCreated: Date
  }
}

Examples:

// Extract and inspect a UPVF file
const upvf = await extractUpvf("drawing.upvf");
console.log(upvf.filename, upvf.content.length, "bytes");

// Extract and attach to email
attachments.push(extractUpvf());
await sendMailTo(["user@example.com"], "UPVF attached", "File Delivery", attachments);

// Extract, process, then delete
const upvf = await extractUpvf();
if (upvf) {
  // ... process file ...
  await deleteUpvf(upvf.fileInfo.ID);
}

PDF Report Generation

Function Description
await createPdfReport(templateName?, ...params) Generates a PDF report for the current ticket

Example:

const report = await createPdfReport();
await sendMailTo(
  ["user@example.com"],
  "Report attached",
  "Ticket Report",
  report
);

E. Reading Current Language

The current user’s language is available via the language variable. The language is sourced from the application’s language setting (configured per user in the application UI). If no language is set, it defaults to "en-US".

In All Contexts (Macros, Listeners, Validators)

console.log(language); // e.g., "en-US", "ko", "de"

const message = language === "ko" ? "승인됨" : "Approved";
await setTicketValue("StatusLabel", message);

Note: The language variable is always defined and never undefined. It defaults to "en-US" when no language is configured.


F. HTTP(S) POSTs and GETs

Custom code can make HTTP requests using the standard fetch API, which is available globally in the Node.js execution environment.

GET Request

const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);

POST Request

const response = await fetch("https://api.example.com/webhook", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    ticketId: await getTicketValue("PunchItem"),
    status: await getTicketValue("Status"),
  }),
});
const result = await response.json();
console.log("Webhook response:", result);

POST with Custom Headers

const response = await fetch("https://internal-api.example.com/notify", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "your-api-key",
  },
  body: JSON.stringify({
    message: "Ticket updated",
  }),
});

if (response.ok) {
  console.log("Notification sent successfully");
} else {
  console.log("Notification failed:", response.status);
}

Practical Example: Sync with External System

// When a ticket is approved, notify an external ERP system
if (userAction.Result === ActionResult.Yes) {
  const ticketData = {
    id: await getTicketValue("PunchItem"),
    tagNo: await getTicketValue("Tag No"),
    status: "Approved",
    approvedDate: Date("yyyy-MM-dd"),
  };

  try {
    const response = await fetch("https://erp.example.com/api/tickets", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(ticketData),
    });

    if (response.ok) {
      await setTicketValue("ERP Sync Status", "Synced");
    } else {
      await setTicketValue("ERP Sync Status", "Failed: " + response.status);
    }
  } catch (error) {
    await setTicketValue("ERP Sync Status", "Error: " + error.message);
  }
}

Note: HTTP requests execute server-side. Ensure target URLs are reachable from the server environment. Internal network addresses and external APIs are both supported.


Field Validators (ValueChangeValidating)

Overview

Field validators enforce custom business rules before field values are saved. When a user changes a ticket field, all active validators for that field execute. If any validator returns false, the change is blocked and the configured error message is displayed.

Navigate to: Settings > Workflows > Validators

Validation-Specific Functions

These functions are available only in field validator custom code:

Function Description
getCurrentFieldValue() Returns the new value the user is trying to save
getFieldValue(fieldName) Returns the current value of any ticket field
getAllFields() Returns an object with all field names and values
hasRole(roleName) Returns true if the current user has the named role

Available Variables

Variable Description
currentValue The value being validated (same as getCurrentFieldValue())
fieldName Name of the field being validated
punchDetails Object with all field values (same as getAllFields())
userRoles Array of the current user’s role names
language Current user’s language code (defaults to "en-US")

Quick Examples

Format validation:

// Ensure uppercase only
return getCurrentFieldValue() === getCurrentFieldValue().toUpperCase();

Role-based restriction:

// Only admins can set to "Critical"
if (getCurrentFieldValue() === "Critical") {
  return hasRole("Admin");
}
return true;

Cross-field validation (select both fields in the Fields TagBox):

// End Date must be after Start Date
if (fieldName === "End Date") {
  const startDate = new Date(getFieldValue("Start Date"));
  const endDate = new Date(getCurrentFieldValue());
  return endDate >= startDate;
}
return true;

Custom Code Editor

The integrated code editor is available in Workflows, Listeners, and Validators pages. It provides:

  • Syntax highlighting for JavaScript
  • Compile button to check for syntax errors
  • Test button to execute code with mock data (no side effects)
  • Function descriptions panel showing all available APIs and their signatures
  • Console output panel showing console.log() output during testing
  • PunchlistID for compilation field to test with a specific ticket’s data

Available Functions Summary

Ticket Management

Function Context Description
await getTicketValue(fieldName) All Read a ticket field value
await setTicketValue(fieldName, value) Macros, Listeners Write a ticket field value
await getPunchlistDetails() All Get all ticket field values
await updatePunchlistDetails(data) Macros, Listeners Batch update ticket fields
await createHiddenField(data) Macros, Listeners Create a hidden ticket-specific field

Workflow Control

Function Context Description
await setStatus(status) Macros, Listeners Set the ticket status
await stepForwardJumpTo(step, message?) Macros, Listeners Jump forward (approval)
await stepBackwardJumpTo(step, message?) Macros, Listeners Jump backward (denial)
await closeWorkflow() Macros, Listeners Close the workflow and ticket
await finishWorkflow() Macros, Listeners Mark the workflow as finished

Communication

Function Context Description
await sendMailTo(to, message, subject, attachments?) Macros, Listeners Send an email
await createComment(message) Macros, Listeners Create a ticket comment

File Operations

Function Context Description
await createPdfReport(template?, ...params) Macros, Listeners Generate a PDF report
await extractUpvf(fileName?) Macros, Listeners Extract a UPVF file
await deleteUpvf(fileID) Macros, Listeners Soft-delete a UPVF file

Validation (Field Validators only)

Function Context Description
getCurrentFieldValue() Validators Get the value being validated
getFieldValue(fieldName) Validators Get any field’s current value
getAllFields() Validators Get all field values as an object
hasRole(roleName) Validators Check if user has a role

Utilities

Function Context Description
Date / Date(format?) All Current date. Use as ${Date} or Date("format") (default: yyyy-MM-dd)
FormatDate(dateInput, format?) All Format any date value (default: yyyy-MM-dd)
ParseFormatDate(str, inFmt, outFmt?) All Parse a date string and reformat it
fetch(url, options?) All Make HTTP requests (GET, POST, etc.)

Available Objects

Object Context Description
userAction.Result Macros The step action result (ActionResult.Yes / ActionResult.No)
ActionResult Macros Enum: Yes, No, Undefined
language All Current user’s language code (defaults to "en-US")
serverAddress All Application server URL from NEXTAUTH_URL, includes instance path (e.g., https://toolkit.example.com/instance2)
UniqueID All Current ticket’s UniqueID (same as this.punchlistID)
attachments Macros, Listeners Pre-defined array for email attachments