Running Scripts
How Script Execution Works
When you click Run in the Execution Console (or when an event handler/job fires), ScriptFabric:
- Validates the script (checks size limit - 100 KB max)
- Creates a sandbox - an isolated JavaScript execution environment
- Injects Canarys HAPI - makes
WorkItems,Users,Groups,Spaces,console, andmakeRequestavailable - Executes your code via the Forge async queue with a 14-minute (840s) execution budget
- Captures output - all
console.log/warn/errorcalls and the finalreturnvalue - Returns results - displays output in the console panel or stores in activity log
The Canarys HAPI Library
Every script has automatic access to the Canarys HAPI (High-level Abstraction Programming Interface). You don't need to import anything - these are global:
WorkItems (Issues)
// Get a single issue by key
const issue = await WorkItems.getByKey('PROJ-123');
console.log(issue.summary, issue.status);
// Get a single issue by ID
const issue2 = await WorkItems.getById('10042');
// Search with JQL
const results = WorkItems.search('assignee = currentUser() AND status != Done');
const count = await WorkItems.count('project = MYPROJ');
// Create an issue
const newIssue = await WorkItems.create('MYPROJ', 'Story', b => {
b.setSummary('Implement login page');
b.setDescription('Build the login UI with email/password fields');
b.setPriority('High');
b.setAssignee('5a1234567890abcdef123456'); // accountId
});
Working with an Issue (HapiWorkItem)
const issue = await WorkItems.getByKey('PROJ-42');
// Update fields
await issue.update(b => {
b.setSummary('Updated summary');
b.setPriority('Critical');
});
// Transition to a new status
await issue.transition('In Progress');
// Add a comment
await issue.addComment('Work has begun on this item.');
// Get comments
const comments = await issue.getComments();
// Read a custom field
const storyPoints = issue.getCustomFieldValue('Story Points');
// Create a sub-task
await issue.createSubTask('Sub-task', b => {
b.setSummary('Research phase');
});
// Link to another issue
await issue.link('Blocks', 'PROJ-99');
// Delete an issue
await issue.delete();
Search Results (HapiSearchResults)
const results = WorkItems.search('project = MYPROJ ORDER BY created DESC');
// Get first N results
const top5 = await results.take(5);
// Get all as array
const all = await results.toArray();
// Process each result
await results.forEach(async (item) => {
console.log(item.key, item.summary);
});
Users
// Current user (Forge app account in console context)
const me = await Users.getLoggedInUser();
// Look up by account ID
const user = await Users.getByAccountId('5a1234567890abcdef123456');
console.log(user.displayName);
const email = await user.getEmailAddress();
const groups = await user.getGroups();
// Search users by display name
const found = await Users.search('Jane');
Groups
const group = await Groups.getByName('developers');
const members = await group.getMembers();
const isMember = await group.contains('5a1234567890abcdef123456');
// Create a new group
const newGroup = await Groups.create('qa-team');
Spaces (Projects)
const project = await Spaces.getByKey('MYPROJ');
console.log(project.name);
// List all projects
const allProjects = await Spaces.list();
// Create a project
const newProj = await Spaces.create('NEWPROJ', 'New Project', b => {
b.setLeadAccountId('5a1234567890abcdef123456');
});
Entity Properties
Entity Properties let you store custom key-value data on any issue, user, or project:
const issue = await WorkItems.getByKey('PROJ-42');
const props = issue.getEntityProperties();
// Store values
await props.setString('lastReviewed', '2024-01-15');
await props.setInteger('reviewCount', 3);
await props.setBoolean('approved', true);
await props.setJson('metadata', { team: 'backend', sprint: 12 });
// Read values
const date = await props.getString('lastReviewed');
const count = await props.getInteger('reviewCount');
const approved = await props.getBoolean('approved');
const meta = await props.getJson('metadata');
// Check existence and list keys
const exists = await props.propertyExists('approved');
const keys = await props.getKeys();
// Remove a property
await props.delete('lastReviewed');
Error Handling
If your script throws an error, ScriptFabric catches it and displays the error message and stack trace in the output panel. You can also use try/catch for controlled error handling:
try {
const issue = await WorkItems.getByKey('NONEXIST-999');
} catch (err) {
console.error('Issue not found:', err.message);
}
Real-World Scenarios
Scenario: Generate a report of overdue issues
const overdue = WorkItems.search(
'due < now() AND status != Done ORDER BY due ASC'
);
const items = await overdue.toArray();
console.log(`=== ${items.length} Overdue Issues ===`);
items.forEach(item => {
console.log(`${item.key} | ${item.summary} | Due: ${item.dueDate}`);
});
Scenario: Reassign all issues from one user to another
const oldUser = '5a111111111111111111111';
const newUser = '5a222222222222222222222';
const issues = WorkItems.search(`assignee = "${oldUser}"`);
let moved = 0;
await issues.forEach(async (issue) => {
await issue.update(b => b.setAssignee(newUser));
moved++;
});
return `Reassigned ${moved} issues`;
Scenario: Secure GitHub Integration & Pull Request Operations
// 1. Retrieve GitHub PAT from Script Variables
const token = await getVariable("GITHUB_TOKEN");
git.token = `${token}`;
// 2. Fetch complete issue object
const issue = await git.getIssue(
"Canarys-Internal-apps",
"Jira_Github_MCP",
2
);
// 3. Fetch comprehensive PR details
const pr = await git.getPR(
"Canarys-Internal-apps",
"Jira_Github_MCP",
3
);
return {
issueKey: issue.key || 'Issue #2',
issueDetails: issue,
prDetails: {
number: pr.number,
title: pr.title,
body: pr.body,
state: pr.state,
mergeable: pr.mergeable,
merged: pr.merged,
author: pr.user?.login,
sourceBranch: pr.head?.ref,
targetBranch: pr.base?.ref,
createdAt: pr.created_at,
updatedAt: pr.updated_at,
url: pr.html_url
}
};