Skip to main content

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-level await support (required for calling async Forge Bridge APIs directly).
  • Globals are on window β€” call view, requestJira, setHeight, etc. directly without any import or window. prefix.
  • No return statement β€” gadgets render HTML; there is no output panel. Use DOM manipulation to display results.
  • Errors are silent by default β€” add window.onerror to 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 is 100%.
  • 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.

GlobalTypeDescription
viewForge view objectAccess 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) => FlagShow a notification flag in the Jira UI
routerForge router objectNavigate to Jira pages programmatically
eventsForge events objectPublish and subscribe to Forge UI events
ModalForge Modal objectOpen Forge modal dialogs
theme{ colorMode: 'light' | 'dark' }Current Atlaskit color mode for dark/light theme support
setHeight(height: string) => voidResize the gadget iframe (e.g. setHeight('300px'))
fetchWeb fetchCall any external HTTP endpoint β€” no authentication injected
consoleBrowser consoleconsole.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 β€” standard fetch RequestInit options (method, headers, body)
  • Returns β€” a standard Response object
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.


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​

  1. Use <script type="module"> for any script that calls Forge Bridge APIs. Only module scripts support top-level await. Plain <script> tags cannot use await at the top level.

  2. Always call setHeight() after rendering content. The iframe defaults to 100% height of its container, which is often too small or too large. Call setHeight(document.querySelector(':root').scrollHeight + 'px') after all DOM mutations.

  3. Add window.onerror to surface errors. Gadgets run in an iframe β€” thrown errors are silent in the host page. Add <div id="errors" style="color:red"></div> and window.onerror = (e) => document.getElementById('errors').textContent = e.toString(); to every gadget that makes API calls.

  4. 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-mode attribute and load the correct token stylesheet. Copy the three-line theme block from the Code Patterns section above.

  5. Check response.status before 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. Handle 404 explicitly for gadget properties (it means the property hasn't been set yet, not an error).

  6. Use router.open() for all in-app navigation. The gadget runs inside an iframe, so standard href clicks are intercepted or open a new tab in some browsers. Use router.open('/jira/dashboards/...') and intercept click events with e.preventDefault().

  7. 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().

  8. 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.

  9. Gadgets are Jira-only. requestConfluence is injected but its calls will fail because the user's session is Jira-scoped. Never call Confluence APIs from a gadget.

  10. External API calls use fetch, not requestJira. 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 native fetch. No Forge authentication is injected for external calls.


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

Forge Bridge APIs​