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
- User clicks a transition button (e.g., "Resolve")
- Jira calls ScriptForge's validator
- Your script runs and either passes (returns truthy / doesn't throw) or fails (throws an error / returns a message)
- If validation passes → transition continues
- 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
- Edit the workflow transition where you want validation
- Add a ScriptForge Validator
- Enter your validation script
- 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