Workflow Validators

Workflow Validators run after a user initiates a transition but before it completes. They check that required conditions are met — if validation fails, the transition is blocked and an error message is shown to the user.

How Validators Work

  1. User clicks a transition button (e.g., "Resolve")
  2. Jira calls ScriptForge's validator
  3. Your script runs and either passes (returns truthy / doesn't throw) or fails (throws an error / returns a message)
  4. If validation passes → transition continues
  5. If validation fails → transition is blocked, user sees the error message

Fail-closed behaviour: If your validator script throws an unexpected error, the transition is blocked. This ensures data integrity — if the system can't verify requirements, it doesn't allow the transition.

Writing a Validator

Validators either return true/nothing (pass) or throw an error with a user-facing message (fail).

Example: Require a comment before resolving

const issue = await WorkItems.getByKey(context.issueKey);
const comments = await issue.getComments();

if (comments.length === 0) {
  throw new Error('You must add a comment explaining the resolution before resolving this issue.');
}

Example: Require a resolution to be set

const issue = await WorkItems.getByKey(context.issueKey);
const resolution = issue.getCustomFieldValue('Resolution');

if (!resolution) {
  throw new Error('Please select a Resolution before closing this issue.');
}

Example: Ensure story points are estimated

const issue = await WorkItems.getByKey(context.issueKey);
const points = issue.getCustomFieldValue('Story Points');

if (points === null || points === undefined || points === 0) {
  throw new Error('Story Points must be estimated before moving to "In Progress".');
}

Example: Prevent transition if linked blockers are unresolved

const issue = await WorkItems.getByKey(context.issueKey);
// Search for blocking issues that link to this one
const blockers = WorkItems.search(
  `issue in linkedIssues("${issue.key}", "is blocked by") AND status != Done`
);
const count = await WorkItems.count(
  `issue in linkedIssues("${issue.key}", "is blocked by") AND status != Done`
);

if (count > 0) {
  throw new Error(`Cannot proceed — ${count} blocking issue(s) are still unresolved.`);
}

Example: Validate a custom field format (email)

const issue = await WorkItems.getByKey(context.issueKey);
const contactEmail = issue.getCustomFieldValue('Contact Email');

if (contactEmail && !contactEmail.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
  throw new Error('Contact Email must be a valid email address.');
}

Configuration

  1. Edit the workflow transition where you want validation
  2. Add a ScriptForge Validator
  3. Enter your validation script
  4. Save the workflow

Tips

  • Keep validators focused — validate one thing per validator for clear error messages
  • The error message from throw new Error('...') is what the user sees — make it actionable
  • Validators run on every transition attempt, so keep them fast
  • You can have multiple validators on a single transition — all must pass