Running Scripts
How Script Execution Works
When you click Run in the Script Console (or when a listener/job fires), ScriptForge:
- Validates the script (checks size limit — 100 KB max)
- Creates a sandbox — an isolated JavaScript execution environment
- Injects HAPI — makes
WorkItems,Users,Groups,Spaces,console, andmakeRequestavailable - Executes your code with a 25-second timeout
- Captures output — all
console.log/warn/errorcalls and the finalreturnvalue - Returns results — displays output in the console panel or stores in execution history
The HAPI Library
Every script has automatic access to the 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, ScriptForge 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`;