Skip to main content

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-level await support (required for calling async Forge Bridge APIs directly).
  • Globals are on window β€” call view, requestConfluence, setHeight, etc. directly without any import or window. prefix.
  • No return statement β€” macros 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 viewer'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.
  • Macros are Confluence-only β€” this module only appears in Confluence; requestJira is 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.

GlobalTypeDescription
viewForge view objectAccess 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) => FlagShow a notification flag in the Confluence UI
routerForge router objectNavigate to Confluence 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 macro 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 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 β€” standard fetch RequestInit options (method, headers, body)
  • Returns β€” a standard Response object; always check response.status before 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.


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​

  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. Call setHeight(document.querySelector(':root').scrollHeight + 'px') after all DOM mutations. For invisible macros, call setHeight('0px') immediately.

  3. Add window.onerror to surface errors. Macros run in an iframe β€” thrown errors are silent to the Confluence page viewer. Add <div id="errors" style="color:red"></div> and window.onerror = (e) => document.getElementById('errors').textContent = e.toString(); to every macro.

  4. 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-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 macro in a broken loading state.

  6. Use requestConfluence, not requestJira. Macros run in a Confluence page; requestJira is injected but will fail because there is no active Jira session. For Confluence APIs, always use requestConfluence.

  7. 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 be undefined if the editor has not configured it. Provide a sensible fallback.

  8. 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/catch and inform the user when an action cannot be completed.

  9. Max 20 macros per Confluence instance. Exceeding the limit is enforced server-side. Disable or delete unused macros before adding new ones.

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


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​