Macros API Reference for AI
This document is a complete API reference and code guide for writing Macros in Script Master β an Atlassian Forge app that lets admins embed custom HTML + JavaScript content directly inside Confluence Cloud pages. A Macro 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 the current Confluence site. Macros are placed on Confluence pages by editors, optionally configured with a free-text Macro Body, and rendered in view mode for all page viewers.
Use this document as context when asking an AI assistant (Claude, Gemini, ChatGPT, etc.) to generate macro code:
Here is the Macros API reference: [link to this file]. Write a macro for [your task].
Execution Modelβ
Macro content is stored as a raw HTML string and injected into an <iframe> via the srcDoc attribute:
<iframe srcDoc={macroContent} width="100%" height={iframeHeight} />
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,requestConfluence,setHeight, etc. directly without any import orwindow.prefix. - No
returnstatement β macros 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 viewer'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. - Macros are Confluence-only β this module only appears in Confluence;
requestJirais injected but cross-product calls will fail. - Macro Body β editors can configure a free-text string per macro placement (e.g. a URL or comma-separated labels), available as
context.extension.config.macroBody.
Available Globalsβ
All globals below are available in every Confluence macro.
| Global | Type | Description |
|---|---|---|
view | Forge view object | Access page context (page ID, space key, etc.) and refresh the current page |
requestConfluence | (path, options?) => Promise<Response> | Authenticated calls to the Confluence REST API |
requestJira | (path, options?) => Promise<Response> | Injected but not useful β Confluence macros run in a Confluence session; Jira API calls will fail |
showFlag | (options) => Flag | Show a notification flag in the Confluence UI |
router | Forge router object | Navigate to Confluence 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 macro 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 page and user. Always call this at the start of a macro script to get entity IDs and the macro body configuration.
const context = await view.getContext();
context shape in Confluence macros:
{
accountId: string; // current viewer'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 macro is rendering
extension: {
isEditing: boolean; // true when the page is in edit mode
space: {
id: string; // numeric space ID
key: string; // space key, e.g. "TEAM"
};
content: {
id: string; // current page ID (numeric string)
};
config?: {
macroId?: string; // ID of the selected macro definition
macroBody?: string; // free-text configured by the page editor
};
macro?: {
isConfiguring: boolean; // true in the macro config dialog
isInserting: boolean; // true when being inserted into a page
};
};
}
Common access patterns:
// Get current page ID
const context = await view.getContext();
const pageId = context.extension.content.id;
// Get current space key and ID
const { space: { key: spaceKey, id: spaceId } } = context.extension;
// Read the Macro Body (configured by the page editor)
const macroBody = context.extension.config?.macroBody ?? '';
view.refresh()β
Refreshes the current Confluence page. Use this after mutations so the host page reflects the changes.
document.getElementById('refresh-btn').addEventListener('click', () => view.refresh());
requestConfluence(path, options?)β
Makes an authenticated HTTP request to the Confluence REST API.
pathβ relative path starting with/wiki/...optionsβ standardfetchRequestInitoptions (method, headers, body)- Returns β a standard
Responseobject; always checkresponse.statusbefore calling.json()
const response = await requestConfluence(`/wiki/api/v2/pages/${pageId}`, {
headers: { 'Accept': 'application/json' },
});
if (response.status !== 200) throw new Error(`API error: ${response.status}`);
const data = await response.json();
showFlag(options)β
Displays a notification flag in the Confluence UI.
showFlag({
id: 'my-flag',
title: 'Done!',
description: 'Labels added successfully.',
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 macro iframe. Call this after rendering content so the iframe fits its contents.
// Fit to content height
setHeight(document.querySelector(':root').scrollHeight + 'px');
// Fixed height
setHeight('300px');
// Hide the macro completely (for invisible/background macros)
setHeight('0px');
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. This is a synchronous value β 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" />`
);
routerβ
The Forge Bridge router for navigating within Confluence. See Forge router docs.
// Navigate to a specific page
router.open(`/wiki/spaces/${spaceKey}/pages/${pageId}`);
eventsβ
The Forge Bridge event bus for publishing and subscribing to Forge 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β
Macro 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 context = await view.getContext();
const response = await requestConfluence(`/wiki/api/v2/pages/${context.extension.content.id}`, {
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: Invisible background macroβ
For macros that perform actions without showing UI (e.g. auto-applying labels), hide the iframe immediately and use showFlag to communicate results:
<script type="module">
setHeight('0px'); // Hide the macro iframe
const context = await view.getContext();
// ... perform work ...
showFlag({
id: 'result',
title: 'Labels updated',
type: 'success',
isAutoDismiss: true,
});
</script>
Pattern: Use Macro Body as configurationβ
The Macro Body (configured by the page editor) is available as context.extension.config.macroBody. Use it for user-configurable parameters like URLs, label lists, or search queries.
const context = await view.getContext();
const macroBody = context.extension.config?.macroBody ?? '';
// Example: parse comma-separated labels
const labels = macroBody.split(',').map(l => l.trim()).filter(Boolean);
// Example: use as a URL
const response = await fetch(macroBody);
Complete Examplesβ
Global variables available in every macroβ
Name: Global variables available in every macro
Description: Displays the global functions and objects accessible in every macro.
<!-- Read more about available Forge Bridge API for Custom UI https://docs.apportunity.xyz/script-master/forge-bridge-front -->
<div>Global functions and objects:</div>
<div id="globals"></div>
<script>
// These global variables are defined in every script macro. You can use them directly without the "window." prefix
const globals = {
view: window.view, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/view/
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/
events: window.events, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/events/
Modal: window.Modal, // https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/modal/
};
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>
Display the ID of the current page and spaceβ
Name: Display the ID of the current page and space
Description: Retrieving the view context to display the ID of the current page and space.
<div>Page ID: <span id="pageId"></span></div>
<div>Space ID: <span id="spaceId"></span></div>
<div>Space Key: <span id="spaceKey"></span></div>
<script type="module">
const context = await view.getContext();
document.querySelector("#pageId").innerHTML = context.extension.content.id;
document.querySelector("#spaceId").innerHTML = context.extension.space.id;
document.querySelector("#spaceKey").innerHTML = context.extension.space.key;
</script>
Retrieving a response from an external URLβ
Name: Retrieving a response from an external URL
Description: Demonstrates how to obtain a URL that users can insert into the Macro Body on the Edit page. This URL is then used to fetch data and display the JSON result on the page.
<div id="output"></div>
<script type="module">
const context = await view.getContext();
console.log('context', context);
const macroBody = context.extension.config.macroBody;
const response = await fetch(macroBody);
const data = await response.json();
document.querySelector("#output").innerHTML = JSON.stringify(data);
</script>
Add labelsβ
Name: Add labels
Description: Reads comma-separated labels from the Macro Body and adds any missing labels to the current page every time the page is opened.
<!--
This example allows you to configure labels, which will always be added to the page on every page view.
It ensures that even if someone deletes the required label accidentally, it will be set back.
Labels should be defined as a comma-separated list in the Macro Body.
-->
<script type="module">
setHeight('0px'); // Set the macro height as small as possible
// Get current labels
const context = await view.getContext();
const response = await requestConfluence(`/wiki/api/v2/pages/${context.extension.content.id}/labels`, {
headers: {'Accept': 'application/json'}
});
const labelsData = await response.json();
// Find labels that should be added
const requiredLabels = context.extension.config.macroBody.split(',').map(labelName => labelName.trim());
const labelsToBeAdded = requiredLabels.filter(requiredLabel => {
return !labelsData.results.some(pageLable => pageLable.name === requiredLabel);
});
// Adding labels
const labelsAddedSuccessfully = [];
for (const labelToAdd of labelsToBeAdded) {
const bodyData = [{
prefix: 'global',
name: labelToAdd
}];
// Do not show any errors if a new label cannot be added, most likely due to lack of permissions
try {
await requestConfluence(`/wiki/rest/api/content/${context.extension.content.id}/label`, {
method: 'POST',
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
body: JSON.stringify(bodyData),
});
labelsAddedSuccessfully.push(labelToAdd);
} catch (e) {
console.error(e);
}
}
// Show a flag if a new label is added
if (labelsAddedSuccessfully.length) showFlag({
id: 'success-flag',
title: 'These labels were added to the page: ' + labelsAddedSuccessfully.join(', '),
type: 'success',
isAutoDismiss: true,
});
</script>
Show random pageβ
Name: Show random page
Description: Retrieves a random page from the current space and displays a link to it along with its content.
<!--
This example demonstrates how to retrieve a random page from the current space (where the macro is opened) and display a link to that page along with its content.
Note: The page content may not always render correctly on another page due to certain macro limitations.
-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@atlaskit/css-reset" />
<div id="page-link" style="color:#0d66e4; cursor:pointer; text-decoration: underline;"></div>
<div id="page-body"></div>
<script type="module">
const context = await view.getContext();
const searchPagesResponse = await requestConfluence(`/wiki/api/v2/pages?sort=modified-date&space-id=${context.extension.space.id}&limit=250`, {
headers: {'Accept': 'application/json'}
});
const pagesData = await searchPagesResponse.json();
const pages = pagesData?.results ?? [];
const randomPage = pages[Math.floor(Math.random() * pages.length)]; // Get random page from all results
const getPageResponse = await requestConfluence(`/wiki/api/v2/pages/${randomPage.id}?body-format=view`, {
headers: {'Accept': 'application/json'}
});
const pageData = await getPageResponse.json();
document.querySelector("#page-link").innerHTML = pageData.title;
document.querySelector("#page-link").addEventListener("click", () => {
router.open(`/wiki/spaces/${context.extension.space.key}/pages/${randomPage.id}`)
});
document.querySelector("#page-body").innerHTML = pageData.body.view.value;
setHeight(document.querySelector(':root').scrollHeight + 'px'); // Resize macro iframe to content
</script>
Writing Good Macros β 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. CallsetHeight(document.querySelector(':root').scrollHeight + 'px')after all DOM mutations. For invisible macros, callsetHeight('0px')immediately. -
Add
window.onerrorto surface errors. Macros run in an iframe β thrown errors are silent to the Confluence page viewer. Add<div id="errors" style="color:red"></div>andwindow.onerror = (e) => document.getElementById('errors').textContent = e.toString();to every macro. -
Apply the dark theme block in every macro 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 macro in a broken loading state. -
Use
requestConfluence, notrequestJira. Macros run in a Confluence page;requestJirais injected but will fail because there is no active Jira session. For Confluence APIs, always userequestConfluence. -
Access Macro Body via
context.extension.config?.macroBody. This is the free-text parameter that page editors can set in the macro configuration dialog. Treat it as an optional string β it will beundefinedif the editor has not configured it. Provide a sensible fallback. -
Scripts run with viewer permissions, not admin permissions. API calls like adding labels or creating pages will fail silently for users without the required Confluence permissions. Wrap write operations in
try/catchand inform the user when an action cannot be completed. -
Max 20 macros per Confluence instance. Exceeding the limit is enforced server-side. Disable or delete unused macros before adding new ones.
-
External API calls use
fetch, notrequestConfluence. The Forge Bridge request function only works with the Atlassian REST API. For third-party APIs (weather services, custom backends, etc.), use the browser's nativefetch. No Forge authentication is injected for external calls.
Useful REST API Linksβ
Confluence REST API v2β
- Get page:
GET /wiki/api/v2/pages/{id}β docs - Get page with body (storage format):
GET /wiki/api/v2/pages/{id}?body-format=storage - Get page with body (ADF format):
GET /wiki/api/v2/pages/{id}?body-format=atlas_doc_format - Get page with body (view/rendered format):
GET /wiki/api/v2/pages/{id}?body-format=view - Get page versions:
GET /wiki/api/v2/pages/{id}?include-versions=true - List pages in space:
GET /wiki/api/v2/pages?space-id={spaceId}&limit=250 - Create page:
POST /wiki/api/v2/pagesβ docs - Update page:
PUT /wiki/api/v2/pages/{id}β docs - Delete page:
DELETE /wiki/api/v2/pages/{id} - List spaces:
GET /wiki/api/v2/spacesβ docs
Confluence REST API v1 (legacy, still needed for some operations)β
- Get page labels:
GET /wiki/api/v2/pages/{id}/labelsβ docs - Add labels to content:
POST /wiki/rest/api/content/{id}/label - Get current user:
GET /wiki/rest/api/user/current - Get users in bulk:
GET /wiki/rest/api/user/bulk?accountId={id}&accountId={id} - Search content (CQL):
GET /wiki/rest/api/content/search?cql=...
Forge Bridge APIsβ
Script Master docsβ
- Macros overview: https://docs.apportunity.xyz/script-master/macros
- Forge Bridge front API: https://docs.apportunity.xyz/script-master/forge-bridge-front