Web Panels
Web Panels display custom content areas within the Jira issue view. Unlike web items (which are clickable actions), web panels show information — HTML, script-generated content, or data pulled from external systems.
Creating a Web Panel
- Go to Apps → ScriptForge → Fragments
- Click Create Fragment
- Set Fragment Type to
web-panel - Choose a Location (typically
issue-panelorissue-glance) - Set the Label (panel heading)
- Choose the content source — static HTML or script
- Save
Locations for Web Panels
| Location | Placement |
|---|---|
issue-panel |
Full-width panel in the issue detail body |
issue-glance |
Compact item in the issue sidebar that expands on click |
Content Types
Static HTML
Display fixed HTML content. Good for informational banners or links.
<div style="padding: 8px;">
<p><strong>Deployment Checklist:</strong></p>
<ul>
<li>Run tests</li>
<li>Update changelog</li>
<li>Tag release</li>
</ul>
</div>
Script-Generated Content
Run a script that returns HTML. The script executes when the panel renders.
// Show the issue's linked PRs from entity properties
const issue = await WorkItems.getByKey(context.issueKey);
const props = await issue.getEntityProperties();
const prUrl = await props.getString('pull-request-url');
if (prUrl) {
return `<a href="${prUrl}" target="_blank">View Pull Request</a>`;
} else {
return '<em>No pull request linked</em>';
}
Real-World Examples
SLA Timer Panel
Shows time remaining until SLA breach:
const issue = await WorkItems.getByKey(context.issueKey);
const created = new Date(issue.created);
const slaHours = 48; // 48-hour SLA
const deadline = new Date(created.getTime() + slaHours * 60 * 60 * 1000);
const now = new Date();
const hoursLeft = Math.round((deadline - now) / (1000 * 60 * 60));
if (hoursLeft <= 0) {
return `<span style="color:red; font-weight:bold;">⚠️ SLA BREACHED (${Math.abs(hoursLeft)}h overdue)</span>`;
} else if (hoursLeft <= 8) {
return `<span style="color:orange;">⏰ ${hoursLeft} hours remaining</span>`;
} else {
return `<span style="color:green;">✅ ${hoursLeft} hours remaining</span>`;
}
Related Issues Summary Panel
Shows a quick summary of linked issues:
const issue = await WorkItems.getByKey(context.issueKey);
const props = await issue.getEntityProperties();
const linksJson = await props.getString('linked-summary');
if (!linksJson) return '<em>No linked issues</em>';
const links = JSON.parse(linksJson);
let html = '<table style="width:100%; font-size:12px;">';
html += '<tr><th>Key</th><th>Type</th><th>Status</th></tr>';
for (const link of links) {
html += `<tr><td>${link.key}</td><td>${link.type}</td><td>${link.status}</td></tr>`;
}
html += '</table>';
return html;
External Service Status Glance
A sidebar glance showing build status from CI/CD:
const issue = await WorkItems.getByKey(context.issueKey);
const props = await issue.getEntityProperties();
const buildStatus = await props.getString('ci-build-status');
if (!buildStatus) return '⚪ No builds';
if (buildStatus === 'passing') return '🟢 Build passing';
if (buildStatus === 'failing') return '🔴 Build failing';
return '🟡 Build in progress';
Conditions
Like web items, panels support conditions to control when they appear:
{
"conditionConfig": {
"issueTypes": ["Bug", "Incident"],
"projectKeys": ["PROD"]
}
}
Tips
- Keep panel content concise — users scan, not read
- Script panels run on every issue view; keep logic fast
- Use entity properties to pre-compute data (via listeners) and just read/display it in the panel
- HTML is sanitized — avoid inline scripts or event handlers