Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Earn badges and make progress

You're on your way to the next level! Join the Kudos program to earn points and save your progress.

Deleted user Avatar
Deleted user

Level 1: Seed

25 / 150 points

Next: Root


1 badge earned


Participate in fun challenges

Challenges come and go, but your rewards stay with you. Do more to earn more!


Gift kudos to your peers

What goes around comes around! Share the love by gifting kudos to your peers.


Rise up in the ranks

Keep earning points to reach the top of the leaderboard. It resets every quarter so you always have a chance!


Adding nested macros to confluence page via API (ScriptRunner)


Hi community,

Just posting a question here since I've been having trouble finding resources for this.

While using scriptrunner to create a page, I can add macros to it. I do it through XML like this:

xml.'ac:structured-macro'('ac:name':"section) {
'ac:rich-text-body'("here are some values to be shown in the body of the macro")
but keep running into problems when I try to add a macro within the body of that first macro. Can someone tell me what I'm missing here or point me at some documentation? Not sure how to bypass the 'WYSIWYG' editor many of these macro's have. Here is an example of one attempt of mine:
xml.'ac:structured-macro'('ac:name':"section") {
'ac:inline'('ac:structured-macro'('ac:name':"column") {
'ac:rich-text-body'("there should be some value in here")
EDIT: Found during testing and looking at other documentation pages that you can specify 'inline' instead of 'rich-text-body' or 'plain-text' and it will read a little bit of the code. It was just reading the string value "there should be some value in here" instead of just "ac:structured-macro". Still not invoking the macro to be nested in the section macro.

3 answers

Posting the solution here. Bear in mind I'm working with groovy in the scriptrunner console in Jira, and not a python file to run on the backend with a curl request:

-Create the page
-Retrieve the page body in storage format. It will be in an XHTML document object.
-Make necessary alterations to the document object
-execute PUT request - see below

Resources that were very helpful!
Document (jsoup Java HTML Parser 1.15.2-SNAPSHOT API) also see 'Element'
And many more forum posts that may not have been directly helpful but gave me a better idea of what I was looking for


def params = [

    type : "page",

    title: pageTitle, // <------- grab the original page title if not changing or specify a new one

    version: [

        number: "2" // <----- I hard incremented during testing. Grab the original page version and + 1 it


    status: "current", // <------------ you can make it a draft if you so please

    body : [

        storage: [

            value         : new_bod, //<-------- literally just take the modified xhtml document and .toString() it

            representation: "storage"



All for this:

I was a bit frustrated you can't just create both the macros at the same time, although now I may try just formatting the page in XHTML in the first place instead of going through an xml writer in the script to create a page initially. If I succeed I will post more info here.
0 votes
Stefan Salzl
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
Nov 08, 2023

Hi @Jordan Hauser 

In your original post you mentioned you are able to embed a macro while creating a page via xml.

Could you please provide an example of code how this would work?


I got stuck on how to add the xml/macro content to the page content:


import com.atlassian.confluence.pages.Page

import com.atlassian.confluence.spaces.SpaceManager

import com.atlassian.confluence.pages.PageManager

import com.atlassian.sal.api.component.ComponentLocator

def pageManager = ComponentLocator.getComponent(PageManager)

def spaceManager = ComponentLocator.getComponent(SpaceManager)

def spaceKey = "STS"

def space = spaceManager.getSpace("${spaceKey}")

def homePage = space.getHomePage()


Page parentPage = pageManager.getPage(;

Page page = new Page();



page.setTitle("Created by Scriptrunner");



pageManager.saveContentEntity(page, null, null);
This is where I am. Tried to add the macro as string but didn´t work.
Any help appreciated.

This appears to be the Confluence api, for this post I was generating the page from our Jira instance. Basically I created the confluence page template manually, then copied the page source into my code and edited certain lines to use variables instead of whatever was in the template.

Whatever you pull from the template page source, try pasting that into the 'page.setBodyAsString('')' function

I tested what you posted in my console and after editing the string page source stuff I had from my original script (basically just removed the variables I had in) and it worked just fine

Here is an example you can paste in that spot, just replace the issue key with some one from your systems (shows up twice, I bolded and underlined them)

"""<p class="auto-cursor-target">

    <ac:structured-macro ac:name="jira" ac:schema-version="1" ac:macro-id="02d7303d-bc19-42c7-b22a-0e8b3a1a5a7b">

        <ac:parameter ac:name="server">Jira</ac:parameter>

        <ac:parameter ac:name="columnIds">



        <ac:parameter ac:name="columns">


        </ac:parameter><ac:parameter ac:name="jqlQuery">key = JTES-2256</ac:parameter>

        <ac:parameter ac:name="serverId">


        </ac:parameter><ac:parameter ac:name="key">





<p><strong>General Ticket Information</strong></p>

<p>Program: </p>

<p>Test Engineers: </p>

<p>Manager Approving: </p>

<p>Created: </p>

<p>Due: </p>

<p>Location: </p>

<p class="auto-cursor-target"><br /></p>

<p><strong>Table Of Contents</strong></p>

<p class="auto-cursor-target"><ac:structured-macro ac:name="toc" ac:schema-version="1" ac:macro-id="1e1c437a-eabb-4530-9862-661b0ba20b75" /></p>

<hr />




<p><br /></p>

<p><br /></p>

<hr />

<h1>Testing Info</h1>


<h2>Required Materials and Equipment</h2>

<h2>Post Testing Info Comparison</h2>

<p><br /></p>

<p><br /></p>

<hr />

<h1>Post Testing Info</h1>

<h2>Additional Info</h2>

<h2>Test Results</h2>


<p><br /></p>

<p><br /></p>

<hr />


<p><ac:structured-macro ac:name="attachments" ac:schema-version="1" ac:macro-id="5be11d15-d16e-49a2-9f57-f9a13f73bbd2" /></p>

<p class="auto-cursor-target"><br /></p>

<p class="auto-cursor-target"><br /></p>

<p><br /></p>

<p><br /></p>"""

Hi @Jordan Hauser , 

This is a very interesting idea! Can you please share with us an example on how you retrieve the storage format of the confluence page you are creating with ScriptRunner? I have a similar request but my solution is not working when I try to create the confluence page in one go with more macros.

Thank you!

Hi @Alexandra-Mihaela Apostol 

When I was updating the page at a later point in the workflow, I have to use a GET request for using the following endpoint:

/plugins/viewstorage/viewpagestorage.action?pageId={page id you want}

The request was structured like this:



//Outside the primary method I'm using in this project found in here Interacting with Confluence from JIRA (

//Most packages I used for this, might be missing a couple

import com.atlassian.applinks.api.ApplicationLink
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType
import com.atlassian.jira.issue.Issue
import com.atlassian.sal.api.component.ComponentLocator
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper


import org.jsoup.*   //This was needed to parse the xhtml
def getPrimaryConfluenceLink() {

def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)

final ApplicationLink confLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)



Within the method I created for this project, I had to do the GET request. 

(1)   confluenceLink.createAuthenticatedRequestFactory()

(2)        .createRequest(Request.MethodType.GET, """plugins/viewstorage/viewpagestorage.action?pageId=""" + holder)

(3)        .addHeader("Content-Type", "application/json")

(4)        .execute(new ResponseHandler<Response>() {

(5)            @Override

(6)            void handle(Response response) throws ResponseException {

(7)                if (response.statusCode != HttpURLConnection.HTTP_OK) {

(8)                    throw new Exception(response.getResponseBodyAsString())

(9)                } else {

(10)                    def xhtml_parser = Jsoup.parseBodyFragment(response.responseBodyAsString)

(11)                    xhtml_parser.outputSettings.prettyPrint(false)




It uses the confluence application link and sets the structure for the request with the factory (1), then creates the GET request referencing the endpoint for the page storage and using a variable for the pageId which you could hardcode if your just doing one (2). I needed to add a header for the request type (3) and you may need to add in some auth if the instance is more secure using another .addHeader("Authorization", "{basic or bearer auth depending on what you need}").

Then it executes the request (4)and says to use a certain variant of the response handler class (5 and 6). 7 and 8 are what to do in case it fails, 9 tells it what to do when it succeeds. 10 is to define a variable thats the body of the page, parsed from the xhtml its formatted in using the jsoup library. 11 sets it to the working format for sending it back when I update the page, if you need to read it in the logs while testing I suggest setting prettyPrint(true) and converting it back to false later.


Hope that helps!


Hi @Jordan Hauser , this worked perfectly for me! 

Thank you for your explanation!



Suggest an answer

Log in or Sign up to answer
AUG Leaders

Atlassian Community Events