Gadgets API Reference for AI
This document is a complete API reference and code guide for writing Gadgets in Script Master β an Atlassian Forge app that lets admins embed custom HTML + JavaScript panels directly on Jira dashboards. A Gadget is a self-contained HTML document (with <script> tags) that runs inside a sandboxed iframe and has direct access to Forge Bridge APIs for reading and writing data in Jira.
Use this document as context when asking an AI assistant (Claude, Gemini, ChatGPT, etc.) to generate gadget code:
Here is the Gadgets API reference: [link to this file]. Write a gadget for [your task].
Execution Modelβ
Gadget content is stored as a raw HTML string and injected into an <iframe> via the srcDoc attribute:
<iframe srcDoc={gadgetContent} width="100%" height={iframeHeight} style="max-height: 960px" />
Forge Bridge globals are injected directly into iframe.contentWindow before the document renders:
Object.assign(iframe.contentWindow, {
setHeight,
requestJira,
requestConfluence,
showFlag,
router,
view,
events,
Modal,
theme,
});
Key implications:
- Write full HTML documents β include
<link>tags for CSS resets,<div>containers, and<script>tags. - Use
<script type="module">to get top-levelawaitsupport (required for calling async Forge Bridge APIs directly). - Globals are on
windowβ callview,requestJira,setHeight, etc. directly without any import orwindow.prefix. - No
returnstatement β gadgets render HTML; there is no output panel. Use DOM manipulation to display results. - Errors are silent by default β add
window.onerrorto catch and display errors in the UI. - The script runs with the current user's permissions β no service account, no elevated access.
- Max iframe height: 960px β use
setHeight()to resize dynamically; the default is100%. - No Node.js APIs (
fs,path, etc.) β browser and Forge Bridge globals only. - Jira only β gadgets appear on Jira dashboards. There is no Confluence or Bitbucket equivalent.
Available Globalsβ
All globals below are available in every Jira gadget.
| Global | Type | Description |
|---|---|---|
view | Forge view object | Access dashboard/gadget context (dashboard ID, gadget ID, account ID, etc.) |
requestJira | (path, options?) => Promise<Response> | Authenticated calls to the Jira REST API |
requestConfluence | (path, options?) => Promise<Response> | Authenticated calls to the Confluence REST API β injected but not useful in a Jira gadget |
showFlag | (options) => Flag | Show a notification flag in the Jira UI |
router | Forge router object | Navigate to Jira pages programmatically |
events | Forge events object | Publish and subscribe to Forge UI events |
Modal | Forge Modal object | Open Forge modal dialogs |
theme | { colorMode: 'light' | 'dark' } | Current Atlaskit color mode for dark/light theme support |
setHeight | (height: string) => void | Resize the gadget iframe (e.g. setHeight('300px')) |
fetch | Web fetch | Call any external HTTP endpoint β no authentication injected |
console | Browser console | console.log(), console.error(), etc. (visible in DevTools) |
API Referenceβ
view.getContext()β
Returns context about the current dashboard and user. Always call this at the start of a gadget script to get entity IDs.
const context = await view.getContext();
context shape in gadgets:
{
accountId: string; // current user's Atlassian account ID
cloudId: string; // Atlassian site cloud ID
siteUrl: string; // e.g. "https://your-org.atlassian.net"
locale: string; // e.g. "en-US"
timezone: string; // e.g. "Europe/Berlin"
moduleKey: string; // which gadget location is rendering
extension: {
dashboard: { id: string }; // current dashboard ID
gadget: { id: string }; // this gadget instance's ID on the dashboard
gadgetConfiguration: {
gadgetId: string; // which gadget definition is selected
showTitle: boolean; // whether the title header is shown
};
entryPoint?: string; // 'edit' when in config/edit mode, undefined in view mode
type: string;
};
}
Common access patterns:
const context = await view.getContext();
const { accountId } = context;
const dashboardId = context.extension.dashboard.id;
const gadgetId = context.extension.gadget.id;
// Check if running in edit/config mode
const isEditMode = context.extension.entryPoint === 'edit';
view.refresh()β
Refreshes the current Jira page. Use after mutations so the host page reflects the changes.
document.getElementById('refresh-btn').addEventListener('click', () => view.refresh());
requestJira(path, options?)β
Makes an authenticated HTTP request to the Jira Cloud REST API v3.
pathβ relative path starting with/rest/api/3/...optionsβ standardfetchRequestInitoptions (method, headers, body)- Returns β a standard
Responseobject
const response = await requestJira('/rest/api/3/myself', {
headers: { 'Accept': 'application/json' },
});
const data = await response.json();
document.getElementById('output').textContent = data.displayName;
Gadget Entity Propertiesβ
Gadgets can store and retrieve persistent key-value data scoped to a specific gadget instance on a specific dashboard using the Jira gadget properties API.
const context = await view.getContext();
const dashboardId = context.extension.dashboard.id;
const gadgetId = context.extension.gadget.id;
// Read a property
const response = await requestJira(
`/rest/api/3/dashboard/${dashboardId}/items/${gadgetId}/properties/config`,
{ headers: { 'Accept': 'application/json' } }
);
// Returns 404 if property does not exist yet
// Write a property
await requestJira(
`/rest/api/3/dashboard/${dashboardId}/items/${gadgetId}/properties/config`,
{
method: 'PUT',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ content: 'my value' }),
}
);
showFlag(options)β
Displays a notification flag in the Jira UI.
showFlag({
id: 'my-flag',
title: 'Saved!',
description: 'Your gadget configuration was saved.',
type: 'success', // 'info' | 'success' | 'warning' | 'error'
isAutoDismiss: true,
});
FlagOptions type:
{
id: string | number;
title?: string;
description?: string;
type?: 'info' | 'success' | 'warning' | 'error';
isAutoDismiss?: boolean;
actions?: Array<{ text: string; onClick: () => void }>;
}
setHeight(height)β
Resizes the gadget iframe. Call this after rendering content so the iframe fits its contents.
// Fit to content height
setHeight(document.querySelector(':root').scrollHeight + 'px');
// Fit to a specific container
setHeight(document.getElementById('content').scrollHeight + 'px');
// Fixed height
setHeight('300px');
The hard maximum is 960px. Passing a larger value will be clamped.
themeβ
An object with the current Atlaskit color mode. Use it to apply dark/light theme styles.
// Apply at the top of every <script type="module"> that renders visible UI
document.querySelector(':root').setAttribute('data-color-mode', theme.colorMode);
document.querySelector(':root').setAttribute('data-theme', `${theme.colorMode}:${theme.colorMode}`);
document.querySelector(':root').insertAdjacentHTML('afterbegin',
`<link rel="stylesheet" href="https://forge.cdn.prod.atlassian-dev.net/atlaskit-tokens_${theme.colorMode}.css" />`
);
routerβ
The Forge Bridge router for navigating within Jira. Use router.open() instead of direct href links because the gadget runs inside an iframe.
// Navigate to a dashboard
router.open(`/jira/dashboards/${dashboardId}`);
See Forge router docs.
eventsβ
The Forge Bridge event bus for publishing and subscribing to UI events. See Forge events docs.
Modalβ
The Forge Bridge modal API for opening dialogs. See Forge Modal docs.
Code Patternsβ
Pattern: Dark theme supportβ
Apply this block at the top of every <script type="module"> that renders visible UI. The theme global is synchronously available β no await needed.
document.querySelector(':root').setAttribute('data-color-mode', theme.colorMode);
document.querySelector(':root').setAttribute('data-theme', `${theme.colorMode}:${theme.colorMode}`);
document.querySelector(':root').insertAdjacentHTML('afterbegin',
`<link rel="stylesheet" href="https://forge.cdn.prod.atlassian-dev.net/atlaskit-tokens_${theme.colorMode}.css" />`
);
Pattern: Error displayβ
Gadget errors are silent by default. Add a visible error container and wire up window.onerror:
<div id="errors" style="color:red"></div>
<script type="module">
window.onerror = (e) => document.getElementById('errors').textContent = e.toString();
// Check HTTP errors explicitly:
const response = await requestJira('/rest/api/3/myself', {
headers: { 'Accept': 'application/json' }
});
if (response.status !== 200) throw new Error(`API error: ${response.status} ${response.statusText}`);
</script>
Pattern: Auto-resize iframe to contentβ
Call setHeight after all DOM mutations are complete so the iframe fits its rendered content:
// After rendering content:
setHeight(document.querySelector(':root').scrollHeight + 'px');
// Or for a specific container:
setHeight(document.getElementById('content').scrollHeight + 'px');
Pattern: Show a loading state then replace with resultsβ
<div id="loading">Loading...</div>
<div id="results" style="display:none"></div>
<script type="module">
// ... fetch data ...
document.getElementById('loading').style.display = 'none';
document.getElementById('results').textContent = result;
document.getElementById('results').style.display = 'block';
setHeight(document.querySelector(':root').scrollHeight + 'px');
</script>
Pattern: Persist data using gadget entity propertiesβ
Use the gadget properties API to store per-gadget configuration (e.g. user-editable content, preferences):
const context = await view.getContext();
const dashboardId = context.extension.dashboard.id;
const gadgetId = context.extension.gadget.id;
const propUrl = `/rest/api/3/dashboard/${dashboardId}/items/${gadgetId}/properties/config`;
// Load
const loadResponse = await requestJira(propUrl, { headers: { 'Accept': 'application/json' } });
if (loadResponse.status === 404) {
// No config stored yet β show initial/edit state
} else {
const { value } = await loadResponse.json();
// value is whatever object you PUT
}
// Save
await requestJira(propUrl, {
method: 'PUT',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ myKey: 'myValue' }),
});
Complete Examplesβ
Global variables available in every Gadgetβ
Description: Displays the global functions and objects accessible in every gadget.
<div>Global functions and objects:</div>
<div id="globals"></div>
<script>
// These global variables are defined in every gadget
const globals = {
requestJira: window.requestJira, // https://developer.atlassian.com/platform/forge/custom-ui-bridge/requestJira/
requestConfluence: window.requestConfluence, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/requestConfluence/
showFlag: window.showFlag, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/showFlag/
router: window.router, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/router/
view: window.view, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/view/
};
const definedElement = (def) => `<div style="color:green"><strong>${def}</strong> is defined</div>`;
const undefinedElement = (def) => `<div style="color:red"><strong>${def}</strong> is undefined</div>`;
for (const [key, value] of Object.entries(globals)) {
if (value) document.getElementById('globals').innerHTML += definedElement(key);
else document.getElementById('globals').innerHTML += undefinedElement(key);
}
</script>
Markdownβ
Description: Embed markdown-formatted documents in your dashboard gadgets. Uses showdown.js to convert markdown to HTML and persists content to the gadget entity property. Supports edit/view toggle and dark mode.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@atlaskit/css-reset" />
<script src="https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js"></script>
<style>
#edit-btn {
display: none;
float: right;
position: absolute;
top: 0;
right: 0;
}
#content:hover + #edit-btn, #edit-btn:hover {
display: inline-block;
}
</style>
<div id="loader" style="display: none">Loading...</div>
<div id="edit-panel" style="display: none">
<div><textarea id="textarea" rows="10" style="width:90%;"></textarea></div>
<div><button id="save-btn">save</button></div>
</div>
<div id="view-panel" style="display: none">
<div id="content"></div>
<button id="edit-btn">edit</button>
</div>
<script type="module">
// Dark theme support
document.querySelector(":root").setAttribute("data-color-mode", theme.colorMode);
document.querySelector(":root").setAttribute("data-theme", `${theme.colorMode}:${theme.colorMode}`);
document.querySelector(":root").insertAdjacentHTML("afterbegin", `<link rel="stylesheet" href="https://forge.cdn.prod.atlassian-dev.net/atlaskit-tokens_${theme.colorMode}.css" />`);
const context = await view.getContext();
const dashboardId = context.extension.dashboard.id;
const gadgetId = context.extension.gadget.id;
const loadContent = async () => {
document.querySelector('#loader').style.display = 'block';
document.querySelector('#edit-panel').style.display = 'none';
document.querySelector('#view-panel').style.display = 'none';
const response = await requestJira(`/rest/api/3/dashboard/${dashboardId}/items/${gadgetId}/properties/config`, {
headers: { 'Accept': 'application/json' }
});
if (response.status === 404) { // property is not found
document.querySelector('#edit-panel').style.display = 'block';
setHeight(document.querySelector('#edit-panel').scrollHeight + 'px');
} else {
const responseData = await response.json();
const { content } = responseData.value;
const html = new showdown.Converter().makeHtml(content);
document.querySelector('#textarea').value = content;
document.querySelector('#content').innerHTML = html;
document.querySelector('#view-panel').style.display = 'block';
setHeight(document.querySelector('#view-panel').scrollHeight + 'px');
}
document.querySelector('#loader').style.display = 'none';
};
await loadContent();
document.querySelector('#save-btn').addEventListener('click', async () => {
await requestJira(`/rest/api/3/dashboard/${dashboardId}/items/${gadgetId}/properties/config`, {
method: 'PUT',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify({ content: document.querySelector('#textarea').value })
});
await loadContent();
});
document.querySelector('#edit-btn').addEventListener('click', async () => {
document.querySelector('#view-panel').style.display = 'none';
document.querySelector('#edit-panel').style.display = 'block';
setHeight(document.querySelector('#edit-panel').scrollHeight + 'px');
});
setHeight(document.querySelector(':root').scrollHeight + 'px');
</script>
My Dashboardsβ
Description: View all dashboards associated with your account in one convenient place. Fetches dashboards owned by the current user and renders them as a navigable list.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@atlaskit/css-reset" /> <!-- Reset fonts, padding, margins to default in Atlaskit -->
<span id="loader">Loading...</span>
<ul id="dashboards" style="margin: 0px; display: none"></ul>
<script type="module">
const context = await view.getContext();
const response = await requestJira(`/rest/api/3/dashboard/search?accountId=${context.accountId}`, {
headers: { 'Accept': 'application/json' }
});
const data = await response.json();
const dashboardLink = (dashboardId, dashboardName) => `<li><a href="/jira/dashboards/${dashboardId}">${dashboardName}</a></li>`;
for (const dashboard of data.values) {
document.getElementById('dashboards').innerHTML += dashboardLink(dashboard.id, dashboard.name);
}
const elements = document.querySelectorAll('#dashboards a');
elements.forEach((item) => {
item.addEventListener('click', (e) => {
e.preventDefault();
router.open(e.target.getAttribute('href'));
});
});
document.querySelector('#loader').style.display = 'none';
document.querySelector('#dashboards').style.display = 'block';
setHeight(document.querySelector('#dashboards').scrollHeight + 'px');
</script>
Integrate External Weather Gadgetβ
Description: Expand Jira's functionality by integrating an external weather gadget from WeatherWidget.io. Demonstrates how to embed third-party widgets directly in a dashboard gadget.
<a class="weatherwidget-io" href="https://forecast7.com/en/40d71n74d01/new-york/" data-label_1="NEW YORK" data-label_2="WEATHER" data-theme="original">NEW YORK WEATHER</a>
<script>
!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src='https://weatherwidget.io/js/widget.min.js';fjs.parentNode.insertBefore(js,fjs);}}(document,'script','weatherwidget-io-js');
</script>
Writing Good Gadgets β Rulesβ
-
Use
<script type="module">for any script that calls Forge Bridge APIs. Only module scripts support top-levelawait. Plain<script>tags cannot useawaitat the top level. -
Always call
setHeight()after rendering content. The iframe defaults to100%height of its container, which is often too small or too large. CallsetHeight(document.querySelector(':root').scrollHeight + 'px')after all DOM mutations. -
Add
window.onerrorto surface errors. Gadgets run in an iframe β thrown errors are silent in the host page. Add<div id="errors" style="color:red"></div>andwindow.onerror = (e) => document.getElementById('errors').textContent = e.toString();to every gadget that makes API calls. -
Apply the dark theme block in every gadget that shows UI. Atlaskit CSS won't respond to the user's dark mode unless you set the
data-color-modeattribute and load the correct token stylesheet. Copy the three-line theme block from the Code Patterns section above. -
Check
response.statusbefore parsing JSON. A non-2xx response may return HTML or a plain text error, not JSON. Calling.json()on an error response throws and leaves the gadget in a broken loading state. Handle404explicitly for gadget properties (it means the property hasn't been set yet, not an error). -
Use
router.open()for all in-app navigation. The gadget runs inside an iframe, so standardhrefclicks are intercepted or open a new tab in some browsers. Userouter.open('/jira/dashboards/...')and intercept click events withe.preventDefault(). -
Do not mutate without a visible outcome. Write/delete operations happen immediately and without confirmation. Always show a result message (success or failure) and, for mutations on the host page, offer a "Refresh" button that calls
view.refresh(). -
Max 20 gadgets per instance. Exceeding the limit at creation time is enforced server-side. The admin UI shows a warning badge. Disable or delete unused gadgets before adding new ones.
-
Gadgets are Jira-only.
requestConfluenceis injected but its calls will fail because the user's session is Jira-scoped. Never call Confluence APIs from a gadget. -
External API calls use
fetch, notrequestJira. The Forge Bridge request functions only work with Atlassian REST APIs. For third-party services (weather widgets, quote APIs, custom backends), use the browser's nativefetch. No Forge authentication is injected for external calls.
Useful REST API Linksβ
Jira Cloud REST API v3β
- Search issues (JQL):
GET /rest/api/3/search?jql=...β docs - Get issue:
GET /rest/api/3/issue/{issueIdOrKey}β docs - Get current user:
GET /rest/api/3/myselfβ docs - Get project:
GET /rest/api/3/project/{projectIdOrKey}β docs - Get all dashboards:
GET /rest/api/3/dashboard/search?accountId={accountId}β docs - Get gadget item properties:
GET /rest/api/3/dashboard/{dashboardId}/items/{itemId}/properties/{propertyKey}β docs - Set gadget item property:
PUT /rest/api/3/dashboard/{dashboardId}/items/{itemId}/properties/{propertyKey}β docs - Delete gadget item property:
DELETE /rest/api/3/dashboard/{dashboardId}/items/{itemId}/properties/{propertyKey}β docs