Running Scripts

How Script Execution Works

When you click Run in the Script Console (or when a listener/job fires), ScriptForge:

  1. Validates the script (checks size limit — 100 KB max)
  2. Creates a sandbox — an isolated JavaScript execution environment
  3. Injects HAPI — makes WorkItems, Users, Groups, Spaces, console, and makeRequest available
  4. Executes your code with a 25-second timeout
  5. Captures output — all console.log/warn/error calls and the final return value
  6. 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`;