This Confluence user macro dynamically loads and renders an OpenAPI (Swagger) specification using Swagger UI.
The macro retrieves the specification URL from macro parameters, validates its availability and JSON structure, and initializes SwaggerUIBundle with deep linking and persistent authorization enabled. It includes loading states with AUI spinner feedback, graceful error handling for missing or invalid specifications, and custom dark-mode styling aligned with Atlassian design tokens to ensure seamless integration with Confluence themes.
#set ($!swaggerUrl = $parameters["url"])
<link
rel="stylesheet"
href="https://unpkg.com/swagger-ui-dist/swagger-ui.css"
/>
<style>
html[data-color-mode="dark"] .swagger-ui .scheme-container,
html[data-color-mode="dark"] #servers {
background: #1c1c21;
}
html[data-color-mode="dark"] .swagger-ui .checkbox p,
html[data-color-mode="dark"] .swagger-ui .dialog-ux .modal-ux-content h4,
html[data-color-mode="dark"] .swagger-ui .dialog-ux .modal-ux-content p,
html[data-color-mode="dark"] .swagger-ui .dialog-ux .modal-ux-header h3,
html[data-color-mode="dark"] .swagger-ui .errors-wrapper .errors h4,
html[data-color-mode="dark"] .swagger-ui .errors-wrapper hgroup h4,
html[data-color-mode="dark"] .swagger-ui .info .base-url,
html[data-color-mode="dark"] .swagger-ui .info .title,
html[data-color-mode="dark"] .swagger-ui .info h1,
html[data-color-mode="dark"] .swagger-ui .info h2,
html[data-color-mode="dark"] .swagger-ui .info h3,
html[data-color-mode="dark"] .swagger-ui .info h4,
html[data-color-mode="dark"] .swagger-ui .info h5,
html[data-color-mode="dark"] .swagger-ui .info li,
html[data-color-mode="dark"] .swagger-ui .info p,
html[data-color-mode="dark"] .swagger-ui .info table,
html[data-color-mode="dark"] .swagger-ui .loading-container .loading::after,
html[data-color-mode="dark"] .swagger-ui .model,
html[data-color-mode="dark"] .swagger-ui .opblock .opblock-section-header h4,
html[data-color-mode="dark"] .swagger-ui .opblock .opblock-section-header > label,
html[data-color-mode="dark"] .swagger-ui .opblock .opblock-summary-description,
html[data-color-mode="dark"] .swagger-ui .opblock .opblock-summary-operation-id,
html[data-color-mode="dark"] .swagger-ui .opblock .opblock-summary-path,
html[data-color-mode="dark"]
.swagger-ui
.opblock
.opblock-summary-path__deprecated,
html[data-color-mode="dark"] .swagger-ui .opblock-description-wrapper,
html[data-color-mode="dark"] .swagger-ui .opblock-description-wrapper h4,
html[data-color-mode="dark"] .swagger-ui .opblock-description-wrapper p,
html[data-color-mode="dark"] .swagger-ui .opblock-external-docs-wrapper,
html[data-color-mode="dark"] .swagger-ui .opblock-external-docs-wrapper h4,
html[data-color-mode="dark"] .swagger-ui .opblock-external-docs-wrapper p,
html[data-color-mode="dark"] .swagger-ui .opblock-tag small,
html[data-color-mode="dark"] .swagger-ui .opblock-title_normal,
html[data-color-mode="dark"] .swagger-ui .opblock-title_normal h4,
html[data-color-mode="dark"] .swagger-ui .opblock-title_normal p,
html[data-color-mode="dark"] .swagger-ui .parameter__name,
html[data-color-mode="dark"] .swagger-ui .parameter__type,
html[data-color-mode="dark"] .swagger-ui .response-col_links,
html[data-color-mode="dark"] .swagger-ui .response-col_status,
html[data-color-mode="dark"] .swagger-ui .responses-inner h4,
html[data-color-mode="dark"] .swagger-ui .responses-inner h5,
html[data-color-mode="dark"] .swagger-ui .scheme-container .schemes > label,
html[data-color-mode="dark"] .swagger-ui .scopes h2,
html[data-color-mode="dark"] .swagger-ui .servers > label,
html[data-color-mode="dark"] .swagger-ui .tab li,
html[data-color-mode="dark"] .swagger-ui label,
html[data-color-mode="dark"] .swagger-ui select,
html[data-color-mode="dark"] .swagger-ui table.headers td {
color: var(--ds-text);
}
.swagger-status {
border: 1px solid #dfe1e6;
border-radius: 6px;
background: #f7f8f9;
padding: 10px;
display: flex;
align-items: center;
gap: 8px;
}
#swagger-ui-container {
display: none;
border: 1px solid #dfe1e6;
border-radius: 6px;
}
</style>
<div id="swagger-status">
<aui-spinner size="small"></aui-spinner>
<div class="aui-message aui-message-info">
<p class="title">
<strong>Loading OpenAPI specification…</strong>
</p>
</div>
</div>
<div id="swagger-ui-container"></div>
<div id="swagger-error-container"></div>
<div id="swagger-data" data-url="$swaggerUrl"></div>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>
<script>
(() => {
const ready = (fn) => {
if (document.readyState !== "loading") {
fn();
} else {
document.addEventListener("DOMContentLoaded", fn);
}
};
ready(async () => {
const status = document.getElementById("swagger-status");
const container = document.getElementById("swagger-ui-container");
const errorContainer = document.getElementById(
"swagger-error-container"
);
const swaggerUrl = document
.getElementById("swagger-data")
.dataset.url.trim();
const renderError = (message) => {
status.style.display = "none";
errorContainer.innerHTML = `
<div
role="note"
aria-labelledby="swagger-error-title"
class="aui-message aui-message-error"
>
<p id="swagger-error-title" aria-hidden="true" class="title">
<strong hidden>Error: </strong>
<strong>Could not reach URL</strong>
</p>
<p>
No OpenAPI / Swagger JSON found at this link.<br/>
Please verify that the URL points directly to a valid
OpenAPI / Swagger JSON file.
</p>
</div>
`;
};
try {
if (!swaggerUrl) {
renderError("Missing specification URL.");
return;
}
const res = await fetch(swaggerUrl);
if (!res.ok) {
renderError("HTTP " + res.status + " returned.");
return;
}
const text = await res.text();
try {
JSON.parse(text);
} catch {
renderError("Invalid JSON document.");
return;
}
status.style.display = "none";
container.style.display = "block";
SwaggerUIBundle({
url: swaggerUrl,
dom_id: "#swagger-ui-container",
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset,
],
layout: "BaseLayout",
persistAuthorization: true,
});
} catch (err) {
renderError();
}
});
})();
</script>Generate a list of all content created by the current user by default, or by a specified user, across your Confluence site
Based on CQL, show a table with the following columns: Page Title, Author, Updated, and Status
Displays a list of pages in a specific space with a specified title or label
Generate IDs in Base32 format
Insert a page break in documents generated from a Confluence page
Use sticky previous/next buttons to navigate through your Confluence page tree in a linear reading order
Generate an alphabetical list of labels from all spaces, linking each label to its corresponding content pages
Convert a specified amount of cryptocurrency to a fiat currency