RFCs are a way for Atlassian to share what we’re working on with our valued developer community. It’s a document for building shared understanding of a topic. It expresses a technical solution, but can also communicate how it should be built or even document standards. The most important aspect of an RFC is that a written specification facilitates feedback and drives consensus. It is not a tool for approving or committing to ideas, but more so a collaborative practice to shape an idea and to find serious flaws early.
Please respect our community guidelines: keep it welcoming and safe by commenting on the idea not the people (especially the author); keep it tidy by keeping on topic; empower the community by keeping comments constructive. Thanks!
Project Summary
Atlassian Connect will reach End of Support (EoS) in December 2026. One of the Connect capabilities without a direct Forge equivalent is the static content macro (staticContentMacro). This RFC introduces the Forge equivalent: staticView, a new opt-in rendering mode for Forge macros where content is stored as ADF (Atlassian Document Format) in Confluence Custom Content and rendered inline without an iframe.
This is an actively tracked feature request (FRGE-1495) that we have accepted and are working towards. Publishing this RFC is part of that process we want to make sure the solution we build addresses your real needs before we finalise the design.
Before we finalise the design, we want to:
-
Clarify our understanding of how partners and customers use Connect static macros today: what content they render, how often they update it, and what scenarios depend on it.
-
Share the proposed Forge staticView design and gather feedback on whether it covers your needs, where the gaps are, and what would make it easier to adopt.
- Publish: 25 May 2026
- Discuss: 8 June 2026
- Resolve: 22 Jun 2026
Part 1: How Connect Static Macros Work Today
If you’re currently using the Connect staticContentMacro module (or have used it in the past), please share your experience by answering the questions in Part 3.
What is the Connect static content macro?
The Connect staticContentMacro module lets apps return pre-rendered Confluence Storage Format (XHTML) from a vendor-hosted endpoint. Confluence fetches this XHTML server-side and renders it directly in the page DOM, with no iframe and no app runtime loaded in the browser. The XHTML response is also cached, so the vendor’s server is not called on every page view.
This made Connect static macros well-suited for:
-
Pages with many macro instances (50 to hundreds), where loading an iframe per instance is too expensive
-
Content that doesn’t change on every page load: static reports, requirement matrices, math expressions, data tables from an external system
-
Scenarios where export fidelity matters: PDF, Word, and email exports rendered the cached XHTML directly
-
Use inside bodied macros like tabs, columns, and expand sections, where nested iframes cause layout and performance issues
Part 2: The Proposed Forge Solution: staticView
How it works
Today, all Forge macros render via a sandboxed iframe loaded in the user’s browser. This has real costs at scale:
-
Performance: Each macro instance spins up its own sandboxed web runtime. A page with 100 macros loads 100 iframes, causing significant CPU, memory, and network overhead, especially on mid-range hardware.
-
No caching: A Forge function can be invoked on every page render. This would result in increased cost for using app macros heavily.
-
Export limitations: PDF export pipelines invoke adfExport functions for each macro, potentially hundreds of parallel Forge function calls, which routinely hit invocation rate limits.
-
Nested macro issues: Iframes inside bodied macros (tabs, expand sections) cause layout shifts and performance problems.
staticView lets a macro store a pre-rendered ADF snapshot as Confluence Custom Content. When a page loads, Confluence fetches the snapshot and renders it directly in the DOM. No iframe, no function call, no app runtime.
The feature has two parts:
-
Manifest declaration: The app declares that its macro has a staticView backed by a confluence:customContent type, and specifies which macro config parameter is used to look up the correct snapshot for each macro instance.
-
Content population: The app is responsible for generating and storing ADF snapshots, either from the frontend when the macro first renders, or from a backend Forge Function for bulk migration or scheduled refresh.
Manifest definition
Here is how a macro with staticView is declared in the app manifest:
modules:
macro:
- key: my-macro
title: My Macro
staticView:
customContentTypeKey: my-static-view-type # links to the custom content type below
contentTitleConfigKey: requirementId # which macro config param is used as the lookup key
confluence:customContent:
- key: my-static-view-type # no 'resource' needed, ADF body only
supportedContainerTypes:
- page # currently only page scope is supported
-
customContentTypeKey: identifies which Custom Content type stores the ADF snapshots. The full stored type key is namespaced as forge:::. See the Custom Content module reference for full manifest options.
-
contentTitleConfigKey: the name of a macro config parameter whose value is used as the title of the Custom Content entry. This is how Confluence maps each macro instance to its corresponding ADF snapshot, without needing to modify the page storage.
-
supportedContainerTypes: Custom Content is scoped to a container type. Currently, only page is supported for staticView, space and blogpost scopes are not yet available. Each snapshot is stored as a child of a specific page. If your app needs to update content, it must update the Custom Content for every page that has a snapshot. You can bulk-fetch all Custom Content entries of a given type using GET /wiki/api/v2/custom-content?type= to find all existing entries and iterate over them. There is no broadcast update mechanism.
How content gets stored and updated
In Connect, the vendor’s server returned fresh XHTML on each render (then Confluence cached it). With Forge staticView, the app stores a snapshot and is responsible for keeping it up to date. There is no automatic re-render. Updates go through the Confluence Custom Content REST API.
Path 1: Frontend (first render populates the snapshot)
When no snapshot exists for a macro instance, the app has two options for how to handle that state in the frontend:
Option A: Automatic fallback to dynamic rendering. The macro renders immediately as a dynamic iframe. The app generates ADF inside the iframe and calls POST /wiki/api/v2/custom-content to store the snapshot. On the next page load, Confluence finds the matching snapshot and renders it inline with no iframe. This is seamless for the user but means the first view always loads an iframe.
Option B: Explicit user-triggered rendering. Instead of automatically loading the iframe, the macro renders a lightweight placeholder with a button (e.g. “Load content” or “Generate view”). The user clicks to trigger the dynamic render, which then generates and stores the snapshot. This is useful when automatic iframe fallback at scale is not acceptable, for example on pages with many uninitialized macro instances where loading all iframes simultaneously would cause performance problems. The tradeoff is that it requires user action before content is visible.
In both options, once the snapshot is stored, subsequent page loads render it inline with no iframe or user interaction needed.
Path 2: Backend Forge Function (bulk generation and scheduled refresh)
-
A Forge Function is triggered by an app lifecycle event, a schedule, or a Confluence event.
-
The function queries the vendor’s data source and generates ADF for each macro instance.
-
The function calls the Confluence Custom Content REST API to create or update snapshots in bulk, keyed by the config parameter value and scoped as children of the relevant Confluence page.
The backend path is critical for migration: it lets vendors pre-populate snapshots for all existing macro instances across all customer pages, so customers don’t need to re-insert or reconfigure their macros.
Path 3: On-demand generation triggered by user or external event
Apps can also provide mechanisms for users or external systems to trigger snapshot generation or updates outside of the automatic paths above:
-
Byline or page action: The app provides a byline button or Confluence page action that a user can click to trigger a Forge Function. The function regenerates and updates the ADF snapshot for all macro instances on that page. This is useful for apps where data freshness matters but a full scheduled refresh is too coarse.
-
Product events: The app listens to Confluence events (e.g. page updated, space events) via Forge event triggers and updates affected snapshots in response. For example, if an app’s macro renders data derived from page content, a page:updated event can trigger a re-generation of the snapshot for that page.
-
External triggers: An external system (e.g. a CI/CD pipeline, a third-party data source) calls the app’s backend, which then calls the Confluence Custom Content REST API to update the relevant snapshots. This is the pattern for apps whose rendered content is driven by data outside Confluence entirely.
Because Custom Content is scoped to a container type, any of these paths must identify which container instances have snapshots to update. Use GET /wiki/api/v2/custom-content?type= to bulk-fetch all entries of a given type and determine which ones need updating.
Rendering behaviour
| Scenario | Behaviour |
|---|---|
| Page view, snapshot exists | ADF snapshot rendered inline. No iframe, no function call. |
| Page view, no snapshot yet | Falls back to dynamic iframe render. |
| Macro inserted or config changed | Dynamic iframe renders; app can store a new or updated snapshot. Snapshot visible on next page load. |
| PDF export | Snapshot used if present. Falls back to adfExport function, then dynamic render. |
| Word export | Requires adfExport to be defined. Static snapshot not used in this path. |
| API/HTML export | Requires adfExport to be defined. Static snapshot not used in this path. |
Known limitations and gaps vs. Connect static macros
We want to acknowledge upfront the differences between our previous staticContentMacro solution on connect and this new staticView proposal for Forge.
Content format: ADF vs. XHTML
Connect static macros returned Confluence Storage Format (XHTML), a rich and expressive format with many years of support. Forge staticView stores content as ADF, the modern Confluence document model. The difference matters:
-
ADF supports: paragraphs, headings, bullet and numbered lists, tables, code blocks, inline code, links, mentions, task lists, panels, media (images), status labels, dates, emoji, horizontal rules, expand sections, and inline SVG
-
ADF does not support (or has limited support for): arbitrary HTML/CSS styling, custom inline styles, multi-column div layouts, JavaScript, custom XHTML elements or namespaced attributes, and some Confluence-native macros embeddable in XHTML
-
Some content expressible in Connect XHTML cannot be faithfully represented in ADF today, particularly richly styled tables, complex visual layouts, and content using Confluence XHTML extensions
-
Media and images: ADF supports inline images and other media via media nodes, but these require assets to be pre-uploaded using the Confluence Media API to obtain media IDs. You then reference those IDs in the ADF. You cannot embed raw image URLs or base64 data directly in ADF media nodes. Inline SVG is an exception: SVG content can be embedded directly in ADF without a separate upload step, which makes it a useful option for vector graphics, icons, and diagrams that would otherwise require a media upload.
Content staleness
With staticView, the snapshot is a point-in-time copy. If the underlying data changes, the app must explicitly update the Custom Content entry via API. There is no mechanism for Confluence to signal to the app that a refresh is needed.
Updates require a page reload
After an app updates a snapshot via the API, the user must reload the page to see the new content. Updated snapshots are not re-rendered without a page reload.
No automatic lifecycle management
The platform does not automatically clean up Custom Content snapshots when a macro is deleted from a page or when the app is uninstalled. Apps are responsible for managing the lifecycle of their stored content. That said, Forge provides the tooling to do this: the preUninstall lifecycle event can be used to clean up snapshots before the app is removed, and the available Confluence product events (e.g. page deleted) can be used to remove or invalidate snapshots in response to content changes.
Page scope only space, blogpost, and global scopes not yet supported
Currently, staticView Custom Content only supports page as the container scope. Space, blogpost, and site-wide (global) scopes are not yet available. In practice this means a snapshot is always stored as a child of a specific page there is no way to define content at a broader scope. If your macro renders content that is the same across multiple pages or spaces (for example, a tenant-wide configuration view, a space-level summary, or content tied to a blogpost), you still need to store and manage a separate snapshot per page. A workspace or installation-level scope is also not supported.
No SDK or bridge helpers yet
Apps must call the Confluence Custom Content REST API directly. There are no Forge bridge methods or SDK helpers for getting, creating, or updating snapshots. This is being considered but not yet implemented.
App type support
Both UI Kit and Custom UI apps are supported.
Word and API export
Word export does not work for Forge macros generally. They export as a placeholder message unless adfExport is defined. This is a known Confluence-level limitation tracked separately.
Migration path from Connect static macros
-
Macro key migration: Existing Connect macro instances on customer pages need their macro keys remapped to the new Forge macro key. The macro key should be the same on both sides. This is handled as part of the broader Connect-to-Forge migration infrastructure as described here.
-
Snapshot pre-population: A Forge Function must generate and store ADF snapshots for all existing macro instances across all customer pages. Because contentTitleConfigKey uses an existing config parameter value as the lookup key, apps can do this in bulk without modifying page storage or asking customers to re-insert macros.
-
Ongoing update strategy: Apps need a plan for keeping snapshots fresh: which Confluence events or schedules trigger a refresh, and how quickly updates need to propagate.
Migration path from Forge dynamic macros
-
Add a confluence:customContent module and required scopes to your manifest. Note: this requires a major version bump.
-
Add the staticView field to your macro definition.
-
Implement ADF generation and snapshot storage, in the frontend, in a backend Function, or both.
-
Run a bulk backfill function to pre-populate snapshots for existing macro instances on customer pages.
A macro can have both a dynamic view and a staticView during rollout. Instances without a snapshot fall back to dynamic rendering automatically.
Part 3: Asks
We’re looking for input from two groups: partners using Connect static macros today, and partners using Forge dynamic macros who might adopt staticView.
About your current Connect static macro usage
-
What content do you render? Describe the type of content your Connect static macro produces: tables, lists, text, images, diagrams, code blocks, styled text, links, embedded Confluence macros. What XHTML elements or patterns are essential to your output?
-
How many macro instances do your customers use on a single page? Give us a sense of scale: typical page has ~5, heavy pages have 50+, edge cases have 100+.
-
How often do you update the static content, and what triggers the update? For example: on every page load, on a schedule (daily/weekly), when underlying data changes, only when the macro is reconfigured, or almost never. What causes an update to be needed?
-
What scenarios are most critical to preserve? For example: export to PDF, use inside tables, use inside bodied macros like tabs or expand sections, specific XHTML elements you depend on.
-
What would break for your customers if the content model changed significantly? Are there aspects of the Connect XHTML model that ADF cannot yet represent?
About the proposed Forge staticView design
-
Does ADF cover what you need to render? If not, what content types or structures are missing? What would be the highest-priority additions?
-
Is the update model (app pushes snapshots via API) workable for your use case? Or do you need a pull model, a webhook, or another mechanism?
-
What Confluence events or triggers would you need to keep snapshots fresh? For example: page created/updated/deleted, macro config changed, space events, a schedule, an app-specific data change, a user action. Which of these are missing from the current set of available Forge triggers?
-
What would make migrating from Connect static macros to Forge staticView easiest? For example: SDK helpers for snapshot management, tooling to scan and backfill existing pages, a migration guide, or platform-assisted macro key remapping.
-
What would make the developer experience of building with staticView better? For example: bridge methods to get/create/update snapshots from within the iframe, a way for the macro instance to set its own snapshot mapping, better debugging tools, or a testing environment.
-
Is the fallback to dynamic rendering (when no snapshot exists) acceptable? Or does it create problems, for example if a page has many unbackfilled macros and falls back to hundreds of iframes at once?
-
Are there use cases you have for Connect static macros that staticView fundamentally cannot serve? If so, what are they, and is there an alternative approach that would work?
-
Does page-level scope work for your use case, or do you need a broader scope? Currently, staticView Custom Content is scoped to page only space, blogpost, and site-wide scopes are not supported. This means a separate snapshot must be stored per page. If your macro renders content that is the same across multiple pages or spaces (for example, organisation-wide data, space-level summaries, or blogpost content), you would need to replicate and keep in sync a snapshot on every page. Does this create a real problem for your app? Which scope would you need space, blogpost, workspace, or installation-level and what use case would it serve?