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

  1. Go to Apps → ScriptForge → Fragments
  2. Click Create Fragment
  3. Set Fragment Type to web-panel
  4. Choose a Location (typically issue-panel or issue-glance)
  5. Set the Label (panel heading)
  6. Choose the content source — static HTML or script
  7. 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>`;
}

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