Panelforge lets Jira administrators add custom widgets ("Panels") to issue screens by writing plain HTML, CSS, and JavaScript — no separate app, no deploy pipeline.
Open Jira Settings → Apps → Panelforge — Panel Builder. There are two kinds of panel:
Each panel has three code tabs (HTML / CSS / JS), a live preview (against sample data or a
real issue key), a placement selector, and per-panel permissions (writable fields, embed
domains). Panels sharing a placement stack by Order; two or more render as
tabs.
Placements: Issue panel (inline on the issue view) · Modal (issue action menu) · Issue glance (sidebar).
jira bridge API
Widget code runs in a locked-down sandbox. Its only connection to Jira is the
jira object, available globally in your panel's JS:
jira.getField(fieldId) → Promise<value>
Read-only access to the current issue's fields, as the viewing user (people never see more
through a panel than Jira already shows them). Field values arrive in Jira REST shapes:
summary is a string, priority is { name },
assignee is { accountId, displayName }, labels is an
array, rich-text fields are ADF documents. The pseudo-field 'key' returns the
issue key (e.g. "PROJ-123").
jira.getField('priority').then(function (p) {
console.log(p && p.name); // "Highest"
});
jira.getUser() → Promise<{ accountId }>The viewing user's account id.
jira.requestAction('updateField', { field, value }) → PromiseRequests a field write. The server allows it only if all of these hold:
Rejections return an error whose message explains why. Plain-string values written to rich-text fields are converted to Atlassian Document Format automatically.
jira.requestAction('updateField', { field: 'duedate', value: '2026-08-05' })
.then(function () { /* saved */ })
.catch(function (e) { console.log(e.message); });
requestAction. State survives reloads,
syncs between viewers, and every change is recorded in the issue history.
jira.embed(url, { height }) → PromiseAsks the trusted shell to render an external page below your widget (external iframes cannot load inside the sandbox itself). The URL's hostname must exactly match one of the panel's Allowed embed domains — https only, no wildcards — and the domain must also be permitted by the installed app version (zero-egress installs block all embeds). Max 3 embeds per panel; repeating a URL updates its height (80–800px).
jira.onIssueChanged(callback)
Registers a callback fired whenever the issue changes (field edits, transitions,
reassignment). Subsequent jira.getField() calls always answer from fresh data.
jira.onIssueChanged(function () { load(); });
Panelforge has no database of its own — panels are the program, Jira is the database. Three kinds of data, three homes:
| Your data | Where it lives | Protected by |
|---|---|---|
|
Panel definitions the code & rules admins write |
Atlassian Forge storage, scoped to your site | Revision history, trash, conflict checks, JSON export |
|
Widget state checklist ticks, saved values |
Your own Jira custom fields, on the issue itself | Jira's permissions, issue history (a free audit trail), your existing backups |
|
Momentary state a running timer, an unsaved slider |
The viewer's browser tab only | Nothing — deliberately ephemeral |
Widgets save into Jira fields so that every change is permissioned, recorded in the issue history, and covered by the backups you already run. Export your panels and back up Jira, and you hold 100% of your data — there is nowhere else it could be.
Ready-to-paste panel templates are available: an escalation-style live banner, SLA countdown ring, cost estimator with guarded write-back, persistent compliance checklist, a domain-rules deadline calculator, and a personal "Mission Control" HUD. An in-app template gallery is on the roadmap — until then, contact support and we'll send you any of them.