Requesting body.view for a body-less legacy macro node on a Forge app returns an export error placeholder

XMLWordPrintable

    • 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

      1. Install a Forge app with a layout: bodied macro whose adfExport handler reads payload.extensionPayload.macro.body
      2. 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
      3. GET /wiki/rest/api/content/{id}?expand=body.view,body.export_view,body.atlas_doc_format
      4. 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.

              Assignee:
              Unassigned
              Reporter:
              James Richards
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated: