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

Using JMWE to Create Confluence Pages from Inside JIRA Issues

Carl Allen June 22, 2020

Create Confluence pages from Jira issues - with links and data - in one click


Greetings Fellow Atlassian Enthusiasts! I think I have something that provided a lot of value to us, and I'd like to share with the community, so other people can make creating documentation from inside of Jira a Snap! It requires executing groovy on a Jira issue in transition, and I used Jira Misc Workflow Extensions to do it.


Over the past year, I got into Groovy scripting with Jira Misc Workflow Extensions (JMWE). This app has genuinely cranked our Jira workflow automation to the max. I keep finding more and more use cases for it in our company. 


This particular use case of JMWE I am proud of the most: we are giving our users the ability to build Confluence pages for customer issues automatically, right from Jira.


We found that it is often hard to get users to leave Jira to create a Confluence page on an issue. To simplify the process, and ensure accuracy, we wanted to add a button right in Jira that would give users a way to generate a new Confluence page automatically, with all necessary issue data, and of course, link it back to that issue itself.


At this point, it is an early proof of concept in our company. However, our testing shows that it is working well and we’ll be expanding it out to all our users shortly. It is just one of our projects made possible with Jira Misc Workflow Extensions (JMWE). Below, I share the Groovy script we use. While the code might seem a bit complicated, I hope that people with some understanding of programming can make use of it.


Here is how we did it

First, we used the “global looping transition” released in Jira, so that the Transition can be executed from anywhere, at any time.


This transition will loop, meaning it will not change the Status. We used it to add a button to the issue detail view to create a confluence page as it requires no status changes. There's a great post about how to use this “global looping transition” on the Atlassian Community that has helped us understand how the concept works.


Then we made it more powerful and automated it with JMWE.


  1. On the transition, we ask the user to select in a custom field (making it required with JMWE) what kind of article they want to create, for example, “How-To guide” or “Incident Report”.
  2. We figure out what project this issue belongs to, and fetch the Confluence Page ID that is associated with this project - this way the pages created all become children of that page.
  3. We create a Confluence page dynamically using the “Storage Format” of our template and inject values from inside Jira, such as the description, the name of the reporter, of the issue perhaps, and anything else needed.
  4. The Confluence page is created AS the user of Jira, so no need to pass authentication back and forth.
  5. Finally, the created page is automatically linked back to the original Jira issue, with a link to it on the page, completing the circle.


Here is what it looks like, The Jira issue is automatically linked to the Confluence page:


The Confluence page is automatically linked to the Jira issue.



The Code for this transition is below, just replace the values after "Equals"

Note: It was cleaned up as it is a bit complicated, but anyone with a basic understanding of programming should be able to make use of it.


If you don’t know how to get the Storage format for a Confluence page:


For jiraServerName and JiraServer here’s how to get your JIRA’s server ID:


Code (code macro hates me):

import org.apache.groovy.json.DefaultFastStringService
import org.apache.groovy.json.FastStringService
import org.apache.groovy.json.FastStringServiceFactory
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 groovy.xml.MarkupBuilder

//Variables to set you only should have to configure Lines 19-31 you can even use Dynamic variables if you have them fielding from custom fields or something.

//This should be the Confluence Space key where the pages will be created.
def confSpace =   "DEMOSPACE" 

// This is the page ID of the confluence page that should be the parent of whatever article is produced.
def parentPageID = 010101010 

// from your Application settings (this setting can be found in Confluence under application links)
def jiraServerName = "Jira Server" 

//from your Jira server (this setting can be found in Confluence under application links)
def jiraServerID = "AAA-BBB-CCC-DDD" 

//Will dictate the type of article, it should be mapped to a jira custom field.
def articleField = issue.getAsString("Custom Dropdown Field") 

// In this example this is a 2 Article setup, 
// for each article setup the articleType# and the option mapped to article Field (Above)
// for pageTitle# set up a dynamic name of the page, in this case it uses the Jira issue Summary to create a unique Name, note it will fail it you try to create a duplicate name

// for each confTemplate# grab the storage format from confluence after making a page, then you may substitute variables (like we did in the example) to make your pages more dynamic.

def articleType1 = "How-To Article"
def pageTitle1 = "How-To " + issue.summary
def confTemplate1 =  '<ac:structured-macro ac:name="info" ac:schema-version="1" ac:macro-id="465846a6-25cb-46ab-a319-9a2c25752959"><ac:rich-text-body>'+ issue.description + '</ac:rich-text-body></ac:structured-macro>'
def articleType2 = "Incident Resolution"
def pageTitle2 = issue.summary + " Incident"
def confTemplate2 = issue.description + ' </ac:rich-text-body></ac:structured-macro><p><br /></p><h2>Steps to Resolve</h2><p><br /></p><h2>Related articles</h2><ac:structured-macro ac:name="info" ac:schema-version="1" ac:macro-id="465846a6-25cb-46ab-a319-9a2c25752959"><ac:rich-text-body>'+ issue.description + '</ac:rich-text-body></ac:structured-macro>'

// ---------- // You don't have to Edit below here, just change the settings above, unless you want to use more than 2 different articleTypes, then you must edit:


//Just setting these Values to something for Error handling

def finalTitle = "1"
def finalTemplate = "1"

// This section Retrieve the primary confluence application link defined above
def ApplicationLink getPrimaryConfluenceLink()
    def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)
    final ApplicationLink conflLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)


def confluenceLink = getPrimaryConfluenceLink()
assert confluenceLink // must have a working app link set up
def authenticatedRequestFactory = confluenceLink.createImpersonatingAuthenticatedRequestFactory()

// Logic to Sort out Dynamic Template and Names, the users are asked what type of Article they are creating on the screen their answer sets the page name and template.

//--- NOTE if you want to use more than 2 article types, add a new elseif block and replace the 2's with 3's after making the section above match.

if(articleField == articleType1)
  finalTitle = pageTitle1
  finalTemplate = confTemplate1

else if(articleField == articleType2)
  finalTitle = pageTitle2
  finalTemplate = confTemplate2

// this Section creates the JIRA issue macro for the Page, linking them together.
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)

xml.'ac:structured-macro'('ac:name': "jira") {
    'ac:parameter'('ac:name': "key", issue.key)
    'ac:parameter'('ac:name': "server", jiraServerName)
    'ac:parameter'('ac:name': "serverId", jiraServerID) // Replace with your Server ID for your instance

// this section links the JIRA Issue with the Defined confluence template
VALUE = writer.toString() + finalTemplate; 

// This part Builds the JSON message from the pieces above.
def params = [
    type : "page",
    title: finalTitle,
    space: [ key: confSpace ],
    ancestors: [
             type: "page",
             id: parentPageID ,
    body : [
        storage: [
            value         : VALUE ,
            representation: "storage"

// Make the connection Above

    .createRequest(Request.MethodType.POST, "rest/api/content")
    .addHeader("Content-Type", "application/json")
    .setRequestBody(new JsonBuilder(params).toString())
    .execute(new ResponseHandler<Response>() {
        void handle(Response response) throws ResponseException {
            if (response.statusCode != HttpURLConnection.HTTP_OK) {
                throw new Exception(response.getResponseBodyAsString())
            } else {
                def webUrl = new JsonSlurper().parseText(response.responseBodyAsString)["_links"]["webui"]




Log in or Sign up to comment
Grant A. Kirkman
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
June 15, 2022

Nice job, Carl! 

Like Carl Allen likes this
Brent Malnack October 14, 2022

I've been working with this for a couple of days. Is it possible to get this to work with Jira and Confluence Cloud? I can run the script successfully, but it never creates anything in Confluence.

Carl Allen October 17, 2022

Completely possible to do it in cloud, but I believe the API calls are completely different for Confluence/Jira Cloud -- we're not migrating to cloud till 2023 so I haven't written a new version yet.


But it is definitely possible. 

AUG Leaders

Atlassian Community Events