Custom Listeners
Custom listeners are user-defined event handlers. You write the script, choose the events, set conditions, and ScriptForge runs your code whenever those events fire.
Creating a Custom Listener
- Navigate to Apps → ScriptForge → Listeners
- Click Create Listener
- Fill in the configuration:
- Name: Give it a meaningful name (e.g., "Auto-assign critical bugs")
- Description: Optional — explain what it does
- Events: Select one or more events from the list
- Projects: Choose specific projects or leave empty for all projects
- Condition: Optional JavaScript expression
- Script: Your automation script
- Run As: Choose
apporuser - Enabled: Toggle on
- Click Save
Configuration Best Practices
Scoping to Projects
If your listener only applies to certain projects, always set the project filter. This prevents unnecessary execution on unrelated events and improves performance.
Using Conditions
Conditions are fast evaluations that determine if the full script should run. Use them to avoid running expensive scripts unnecessarily:
// Only fire for Bugs with High priority
event.issue.fields.issuetype.name === 'Bug' &&
event.issue.fields.priority.name === 'High'
Run As Modes
app— The script runs with the Forge app's service account. It can access all projects, issues, and users. Use this for administrative tasks.user— The script runs with the permissions of the user who caused the event. Use this when you want permission-aware behavior (e.g., the script can only modify what that user can modify).
Real-World Scenarios
Scenario: Auto-label issues based on summary keywords
Purpose: When someone creates an issue with "frontend" in the summary, add the "frontend" label automatically.
const issue = await WorkItems.getByKey(event.issue.key);
const summary = issue.summary.toLowerCase();
if (summary.includes('frontend')) {
await issue.update(b => {
b.setLabels([...issue.labels, 'frontend']);
});
}
if (summary.includes('backend')) {
await issue.update(b => {
b.setLabels([...issue.labels, 'backend']);
});
}
Event: avi:jira:created:issue
Condition: None (checks keywords inside the script)
Scenario: Notify via comment when overdue issues are transitioned
Purpose: If an issue whose due date has passed gets moved to "In Progress", add a comment warning the team.
const issue = await WorkItems.getByKey(event.issue.key);
const dueDate = new Date(issue.dueDate);
const now = new Date();
if (dueDate < now && issue.status === 'In Progress') {
await issue.addComment(
'⚠️ This issue is past its due date. Please update the timeline or escalate.'
);
}
Event: avi:jira:updated:issue
Condition: event.changelog && event.changelog.items.some(i => i.field === 'status')
Scenario: Clone issue structure to another project
Purpose: When an Epic is created in the template project, clone it and its child stories to the target project.
const epic = await WorkItems.getByKey(event.issue.key);
const children = WorkItems.search(`"Epic Link" = ${epic.key}`);
const newEpic = await WorkItems.create('TARGET', 'Epic', b => {
b.setSummary(epic.summary);
b.setDescription(epic.description);
});
await children.forEach(async (child) => {
await WorkItems.create('TARGET', 'Story', b => {
b.setSummary(child.summary);
b.setDescription(child.description);
});
});
console.log(`Cloned epic with ${await children.count} stories`);
Event: avi:jira:created:issue
Condition: event.issue.fields.issuetype.name === 'Epic'
Projects: Template project only
Editing and Disabling
- Click any listener in the list to edit its configuration
- Use the Enabled toggle to temporarily disable a listener without deleting it
- Disabled listeners still appear in the list but do not fire on events
Execution Logs
Every time a custom listener executes, it creates an entry in the Execution History with:
- The event that triggered it
- The listener name
- Success/failure status
- Console output and errors
- Execution duration