-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Low
-
Component/s: Extensibility - Macro
-
Minor
Issue Summary
Confluence invokes a Forge macro's adfExport function for ADF "extension" (non-bodied) nodes, even though the function exists only because the macro declared layout: bodied. For these nodes extensionPayload.macro is omitted entirely — not null, not an empty object — so a handler reading macro.body throws, and Confluence injects its own error placeholder into body.view and body.export_view.
A body-less ac:structured-macro converts to an "extension" node rather than a "bodiedExtension", but is dispatched to the export function anyway. extensionType is com.atlassian.confluence.macro.core in both the passing and failing variants, so apps have no attribute to branch on.
The macro module reference describes macro.body as "available for layout: bodied macros only" — a statement about the macro type, so vendors read it as a per-instance guarantee. Atlassian's own export sample (forge-rich-text-custom-config-macro, src/export-macro.js) dereferences macro.body unguarded.
Under normal circumstances, creating a macro through the UI will always create a macro with
<ac:rich-text-body> <p local-id="f45572bfaa36" /> </ac:rich-text-body>
This only occurs when the macro is created by other means.
Steps to Reproduce
- Install a Forge app with a layout: bodied macro whose adfExport handler reads payload.extensionPayload.macro.body
- Create a page via POST /wiki/rest/api/content with representation: storage, containing two instances of that macro that differ only in the presence of ac:rich-text-body — variant A without it, variant B with a table inside it
- GET /wiki/rest/api/content/{id}?expand=body.view,body.export_view,body.atlas_doc_format
- Inspect the response and the app's Forge logs
Expected Results
Either adfExport is not dispatched for a node that structurally cannot carry a body, or it is dispatched with a guard-friendly payload such as macro:
{ body: null }and a documented "nothing to export" return value.
Actual Results
Variant A converts to an "extension" node with no content; variant B to a "bodiedExtension". Both carry extensionType com.atlassian.confluence.macro.core. The export function is invoked for both, and for variant A the payload is { extensionPayload: { config: {} } } with no macro key. The handler throws "Cannot read properties of undefined (reading 'body')" with errorType FUNCTION_ERROR_HOSTED_APP_CODE and caller.issuer micros-group/confluence.
Both body.view and body.export_view then return, for variant A only:
<div>We've encountered an issue exporting this macro. Please try exporting again later.</div>
PDF export omits the macro silently. The placeholder reaches consumers of the rendered body over REST — Search API, previews, third-party exporters.
Workaround
App vendors must guard the property rather than relying on the documented layout: bodied behaviour, reading payload?.extensionPayload?.macro?.body and returning "" when it or its content is absent. Returning "" is safe for this signature because the body is absent from the storage format too, so there is no content to lose. Note that content must be guarded as well as body.