For the complete documentation index, see llms.txt. This page is also available as Markdown.

Authoring Custom Validations

Custom validations are JavaScript functions that run during change order validation. They receive the full change order context — items, attributes, approval stages, custom fields — and return a pass/fail result with a message. You write the logic; Duro handles execution in a secure sandbox.

How It Works

When a change order is validated, Duro sends each custom validation rule to an isolated execution environment. Your code receives a data object containing the change order and its items, runs your logic, and returns a result. The entire round-trip happens within a 30-second timeout.

The basic contract is simple: export a validate function that accepts the data payload and returns { valid, message }.

exports.validate = async function(data) {
  // data.change_order — the change order metadata
  // data.items — the components in the change order

  // Your logic here...

  return {
    valid: true,      // or false
    message: 'Human-readable result'
  };
};

The Validation Payload

Your validation function receives a single data argument with two top-level properties:

  • data.change_order — the change order metadata, approval stages, custom fields

  • data.items — the components in the change order, with full attribute data

Change Order Fields

Field
Type
Description

id

string

UUID of the change order

name

string

Change order name/title

description

string

Description text (can be null)

status

string

draft, open, resolved, closed, or on_hold

resolution

string

pending, approved, rejected, or withdrawn

createdBy

object

{ id, name, primaryEmail } of the creator

updatedBy

object

{ id, name, primaryEmail } of the last editor

contents

array

Custom form fields with values and validation rules

stages

array

Approval stages with reviewers and notification lists

Item Fields

Each entry in data.items combines the change order item data with its component information:

Field
Type
Description

id

string

Change order item UUID

itemId

string

Component UUID

name

string

Component name

description

string

Component description

category

object

{ id, name, attributes } — category info with attribute definitions

status

object

{ id, name, mapsTo, color } — current status

version

number

Component version number

revisionValue

string

Current revision (e.g., "A", "B")

state

string

RELEASED or MODIFIED

attributes

object

Human-readable attribute values keyed by name

attributeValues

object

Raw attribute values keyed by UUID (legacy)

proposedRevision

string

New revision value

proposedStatus

object

{ id, name } — new status

Component Attributes

Each item carries attribute data in two forms.

item.attributes — Human-readable, keyed by name. This is what you'll use most of the time.

All attributes assigned to the item's category appear in this map. Unset attributes are null — so you can distinguish "not set" from "attribute doesn't exist on this category."

item.category.attributes — Attribute definitions for the category. Useful when you need to know what attributes exist, their types, or iterate programmatically.

Each definition includes: id, name, type, source, sourceKey, unit.

item.attributeValues — Raw storage format with UUID keys. Kept for backward compatibility. You shouldn't need this unless you're working with attribute IDs directly.

Value Shapes

Attribute Type

Example item.attributes['Name']

STRING, URL, EMAIL, PHONE, DATE, BOOLEAN

"Acme Co" (unwrapped primitive)

NUMBER (simple)

42 (unwrapped number)

NUMBER (measured -- mass, length)

{ value: "25", unit: "g" }

NUMBER (currency)

{ value: 42.50, unit: "USD" }

LIST

"Each" (unwrapped string)

Unset

null

Writing Your First Validation

Create a .js file that exports a validate function. The function must be async (or return a Promise) and return an object with valid (boolean) and message (string).

Example: Block Components Missing a Manufacturer

That's the whole file. No imports, no boilerplate, no class wiring. Duro loads it, calls exports.validate, and records the result.

Common Patterns

Checking Attribute Values

Checking Category-Specific Rules

Use item.category.name to apply rules only to certain component types:

Validating Approval Stages

The data.change_order.stages array gives you full visibility into the approval workflow:

Enforcing Naming Conventions

Working with Measured Values

Measured attributes (mass, length, etc.) return { value, unit } rather than a bare number. Parse accordingly:

Logging

All console methods are captured and visible in the validation results:

  • console.info() — informational messages about execution

  • console.log() — general-purpose logging

  • console.warn() — warnings about potential issues

  • console.error() — error details

Use logging liberally. It costs nothing and makes debugging much easier when a validation fails unexpectedly.

Logs are accessible via the GraphQL API on each validation result's logs field — see Change Order Validations for the query structure.

Linking to Entities

Duro supports a markdown-inspired syntax for creating clickable links in validation messages and logs. When you reference a component or change order, wrap it in a link so users can click straight through to it in the Duro UI.

Syntax

Entity
Syntax
Example

Component

[text](item:component/id)

[CAP-001](item:component/abc-123)

Assembly

[text](item:assembly/id)

[Main Board](item:assembly/xyz-789)

Change Order

[text](co:changeorder/id)

[ECO-2024-001](co:changeorder/co-123)

User

[text](user:profile/id)

[Jane Smith](user:profile/user-456)

Helper Function

A small helper keeps link construction consistent:

Links degrade gracefully — if the UI can't parse them, users still see the display text.

Error vs Warning Mode

The onFailure setting controls what happens when your validation returns { valid: false }:

Mode
Behavior

error

Blocks the change order from proceeding (hard stop)

warning

Alerts reviewers but does not block progress

This is a configuration setting, not something you control in JavaScript. Your code is identical either way — you return { valid: false, message } and the platform decides whether to block or warn based on the mode.

If you manage validations through the library-validations-template, the mode is set in validations.yaml via the onFailure field.

Execution Environment

Validations run in an isolated sandbox with a 30-second timeout. No filesystem access, no network access (unless your admin has enabled the fetch proxy). All console output is captured.

Your code cannot:

  • Read or write files

  • Make HTTP requests (by default)

  • Import Node.js built-in modules

  • Access environment variables

If your validation needs external data, talk to your Duro admin about enabling the fetch proxy.

Managing Validations via the API

You can create, update, and list custom validation rules through the GraphQL API. The library is resolved from your API key — you don't need to specify it explicitly.

Creating a Validation Rule

Variables:

Field
Type
Description

name

string

Display name for the validation rule

description

string

What the rule checks

code

string

The JavaScript source code

type

string

Always "custom"

onFailure

string

"ERROR" (blocks) or "WARNING" (non-blocking)

isActive

boolean

Whether the rule runs during validation

version

string

Semantic version for tracking changes

Updating a Validation Rule

You can update any combination of code, description, onFailure, isActive, and version. The name and type cannot be changed after creation.

Listing Validation Rules

Deleting a Validation Rule

Source-Controlled Validations

For teams that want to manage validations in version control, Duro provides the library-validations-template — a GitHub template repository you can fork for your library. It gives you:

  • A validations.yaml file defining each rule's name, description, version, active state, and failure mode

  • A validations/ directory for your JavaScript files

  • A sync script that pushes your local definitions to the Duro API via the mutations above

  • A GitHub Actions workflow that syncs automatically on push to main

Fork the template, add your API key as a GitHub secret (DURO_LIBRARY_API_KEY), and every push to main will sync your validation rules to your Duro library. The sync is additive — it creates new rules, updates changed ones, and deactivates rules removed from the config (it never deletes).

This approach lets you review validation changes in pull requests, track history in git, and deploy consistently across libraries. The repository also includes a CLAUDE.md file that enables AI-assisted validation authoring — describe what you want in natural language and Claude Code generates the JavaScript and YAML config.

Best Practices

Keep validations focused. One validation, one concern. A rule that checks manufacturer data and also enforces naming conventions should be two separate validations. Smaller validations are easier to debug, toggle, and explain to users.

Provide clear messages. Your message is the first thing a user sees when something fails. Tell them what's wrong and which components are affected — not just "validation failed."

Use component links. When referencing specific components in messages or logs, use the [name](item:component/id) syntax so users can click through to the problem.

Handle null and undefined gracefully. Attributes can be null (unset), items can have missing fields, and the stages array can be empty. Defensive checks prevent runtime errors that surface as cryptic failures.

Log diagnostic info. Drop console.info calls at the start of your validation to record how many items you're checking, which categories are present, and any thresholds you're using. When something goes wrong, the logs tell the story.

Return a message on success too. A passing message like "All 12 components have a manufacturer" gives users confidence that the check actually ran and inspected their data.

Last updated

Was this helpful?