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!


Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
Community Members
Community Events
Community Groups

Customize pdf export using Script Runner for Confluence Server

Ramakrishnan Srinivasan
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
Sep 01, 2020


I have made some customization of pdf export of a Confluence page. Sharing my approach here for others to try/benefit.

My Setup:

  • Confluence Server - 7.3.2
  • Script Runner for Confluence - 6.2.0-p5
  • Table Filter and Charts for Confluence - 5.3.25
  • Confluence has HTML macro enabled

My Approach:

  • Create Custom Menu to do the job
  • Create a temporary page which can be trashed once export job is done
  • Get the page content as string, customize it (in my case remove jira issues hyperlinks)
  • Edit the temporary page and update it with customized content, redirect the page;
  • With delay from javascript redirect it to pdf export link again

My Configurations

  • In Confluence Script Runner - Fragment -Create Script Fragments - Custom WebItem
    • What Section this should go in - system.content.action/secondary
    • Key: external-pdf
    • Menu Text - Custom
    • Weight - 1
    • Condition (such that this custom menu is displayed only in some pages)
      • if( == "SOME_SPACE_KEY" &&"SOME UNIQ TITLE STRING ") && !("NEGATE TITLE"))) {
        return true
        } else {
        return false
    • Do What - Navigate to Link
    • Link - https://HOST/wiki/rest/scriptrunner/latest/custom/exportPage_toPDF?spaceKey=<SOME SPACE KEY>&parentPageTitle=${page.title}
  • In Confluence Script Runner - Jobs - setup to trash the temporary page
    • import com.atlassian.sal.api.component.ComponentLocator
      import com.atlassian.confluence.spaces.Space
      import com.atlassian.confluence.spaces.SpaceManager
      import com.atlassian.confluence.core.DefaultDeleteContext
      import com.atlassian.confluence.pages.Page
      import com.atlassian.confluence.pages.PageManager

      def spaceManager = ComponentLocator.getComponent(SpaceManager)
      def pageManager = ComponentLocator.getComponent(PageManager)

      def parentPage=pageManager.getPage("SOME_SPACE_KEY", "TO DELETE PAGES")
      def childPages = pageManager.getDescendants(parentPage)

      if(childPages) {
           childPages.each { thisChild ->
                     pageManager.trashPage(thisChild, DefaultDeleteContext.DEFAULT)

  • In Confluence Script Runner - REST Endpoint
    • //This script is loaded in Confluence - Script Runner - Rest End Point
      //It takes the spaceKey and page title as url parameters
      //gets page content
      //replacesAll href of jira issue links
      //relacesAll Back to Top CDATA
      //creates a child page from replaced string
      //redirects that child page for pdf download after the page is loaded in browser

      /*import org.apache.log4j.Level
      import org.apache.log4j.Logger
      def logx = Logger.getLogger("com.acme.workflows")

      import org.apache.http.HttpRequest
      import org.apache.http.protocol.HttpContext
      import groovy.json.JsonBuilder
      import groovy.json.JsonSlurper
      import org.apache.http.HttpRequestInterceptor
      import net.sf.json.JSONArray
      import static
      import static
      import groovy.transform.Field

      import org.codehaus.groovy.runtime.MethodClosure
      import org.apache.http.HttpEntity;
      import org.apache.http.HttpEntityEnclosingRequest


      import com.atlassian.confluence.setup.settings.SettingsManager
      import com.atlassian.confluence.core.BodyContent
      import com.atlassian.confluence.core.BodyType
      import com.atlassian.confluence.core.DefaultSaveContext
      import com.atlassian.confluence.core.DefaultDeleteContext
      import com.atlassian.confluence.pages.DuplicateDataRuntimeException
      import com.atlassian.confluence.pages.Page
      import com.atlassian.confluence.pages.PageManager
      import com.atlassian.confluence.pages.templates.PageTemplateManager
      import com.atlassian.confluence.spaces.Space
      import com.atlassian.confluence.spaces.SpaceManager
      import com.atlassian.confluence.user.AuthenticatedUserThreadLocal
      import com.atlassian.sal.api.component.ComponentLocator
      import com.onresolve.scriptrunner.canned.confluence.utils.PermissionDeniedException
      import groovy.json.JsonOutput
      import groovy.transform.BaseScript
      import groovy.transform.Field
      import org.apache.log4j.Logger


      import java.text.SimpleDateFormat

      import groovy.time.TimeCategory
      import groovy.time.TimeDuration

      Date start = new Date()

      @Field SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)
      @Field PageManager pageManager = ComponentLocator.getComponent(PageManager)
      @Field PermissionManager permissionManager = ComponentLocator.getComponent(PermissionManager)
      @Field PageTemplateManager pageTemplateManager = ComponentLocator.getComponent(PageTemplateManager)
      //@Field Logger log = Logger.getLogger("com.onresolve.scriptrunner.runner.ScriptRunnerImpl")

      @BaseScript CustomEndpointDelegate delegate

      //End point name is the same as the method's name
      //exportPage_toPDF_withoutJira_hyperLinks(httpMethod: "GET", groups: ["confluence-administrators", "confluence-users"]) { MultivaluedMap queryParams, String body ->
      exportPage_toPDF_withoutJira_hyperLinks(httpMethod: "GET") { MultivaluedMap queryParams, String body ->

      Date date = new Date()
      String datePart = date.format("dd/MMM/yyyy")
      String timePart = date.format("HH:mm:ss.SSS")
      String dateTimeStamp = date.format("dd/MMM/yyyy HH:mm:ss.SSS")

      pageManager = ComponentLocator.getComponent(PageManager)

      def spaceKey = queryParams.getFirst("spaceKey").toString()
      def parentPageTitle = queryParams.getFirst("parentPageTitle").toString()

      try {
      parentPage=pageManager.getPage(spaceKey, parentPageTitle)
      def contentEntityObject = parentPage.getEntity()
      def parentPageBody=parentPage.getBodyAsString()

      def space = spaceManager.getSpace(spaceKey) as Space
      def childPageTitle = parentPageTitle + " - External"
      def childPage = pageManager.getPage(spaceKey, childPageTitle)

      if(childPage) {
      pageManager.trashPage(childPage, DefaultDeleteContext.DEFAULT)

      def HTML_A_HREF_TAG_PATTERN_jira = "\\s*(?i)href\\s*=\\s*\"([^\"]*jira/browse/[^\"]*\")"
      def regex_BacktoTop = "<p><ac:link ac:anchor=\"_wiki_toc\"><ri:content-entity ri:content-id=\"\\d+\" /><ac:plain-text-link-body><!\\[CDATA\\[Back to Top\\]\\]></ac:plain-text-link-body></ac:link></p>"

      def childPageContent = parentPageBody.replaceAll(HTML_A_HREF_TAG_PATTERN_jira, "dummy")
      childPageContent = childPageContent.replaceAll(regex_BacktoTop, "")

      def regex_jiraMacro = "<p style=\"font-size: 15.0px;\"><strong>G3 Issue used to create this Release Page</strong></p><p><ac:structured-macro ac:name=\"jira\" ac:schema-version=\"\\d+\" ac:macro-id=\"([a-zA-Z0-9*--]+)\"><ac:parameter ac:name=\"server\">Jira</ac:parameter><ac:parameter ac:name=\"serverId\">([a-zA-Z0-9*--]+)</ac:parameter><ac:parameter ac:name=\"key\">((?<!([A-Za-z]{1,10})-?)[A-Z]+-\\d+)</ac:parameter></ac:structured-macro></p>"
      childPageContent = childPageContent.replaceAll(regex_jiraMacro, "")

      def regex_history = "<h1>Document history</h1><p><ac:structured-macro ac:name=\"version-history\" ac:schema-version=\"\\d+\" ac:macro-id=\"([a-zA-Z0-9*--]+)\" /></p>"
      childPageContent = childPageContent.replaceAll(regex_history, "")

      def toDelete_parentPage=pageManager.getPage(spaceKey, "TO DELETE PAGES")

      def createdChildPageId = createBasicPage(space, toDelete_parentPage, childPageTitle, "Temporary Page")

      def tableFilterString = "<ac:structured-macro ac:name=\"table-filter\" ac:schema-version=\"1\""

      def loadResources = "" //tf-export-ready is Table filter element

      if(childPageContent.contains(tableFilterString)) {
      loadResources = "<ac:structured-macro ac:name=\"html\">\
      <script type=\"text/javascript\">\
      AJS.toInit(function() {\
      AJS.bind('tf-export-ready', function() {\
      setTimeout(function() {AJS.\$(location).prop('href', 'https://HOST/wiki/spaces/flyingpdf/pdfpageexport.action?pageId=" + createdChildPageId + "');}, 2000);\
      } else {
      //removed AJS.bind('tf-export-ready', function() {\
      loadResources = "<ac:structured-macro ac:name=\"html\">\
      <script type=\"text/javascript\">\
      AJS.toInit(function() {\
      setTimeout(function() {AJS.\$(location).prop('href', 'https://HOST/wiki/spaces/flyingpdf/pdfpageexport.action?pageId=" + createdChildPageId + "');}, 2000);\

      childPageContent = childPageContent.replaceAll("dummy", "") + loadResources

      editPage("${createdChildPageId}", childPageTitle, childPageContent, "2")

      pageUrl = "https://HOST/wiki/pages/viewpage.action?pageId=${createdChildPageId}"
      return Response.temporaryRedirect(URI.create(pageUrl)).build()
      } catch(Exception ex) {
      //pageIdMap["Exception"] = "Exception to get page or append page for spaceKey = ${spaceKey} and title = ${title}: ${ex.toString()}"

      * Create a basic page. This is not linked in any hierarchy.
      * @param space The space that this page belongs to.
      * @param title The title of the page we are creating.
      * @param content The content of the page we are creating.
      * @return The create page object.
      def createBasicPage(Space space, Page parentPage, String title, String content) {
      def pageManager = ComponentLocator.getComponent(PageManager)
      def page = new Page()
      def bodyContent = new BodyContent(page, content, BodyType.XHTML)
      page.with {
      linkPages(parentPage, page)
      pageManager.saveContentEntity(page, DefaultSaveContext.SUPPRESS_NOTIFICATIONS)


      * Link a parent and a child page together. This method creates a bi-directional relationship between the two pages.
      * @param parent The parent page that we wish to link.
      * @param child The child page that we wish to link.
      void linkPages(Page parent, Page child) {
      // Set the parent page on the child
      // Set the child page on the parent
      // Set the ancestors on the child page
      def ancestors = []
      def parentPageAncestors = parent.ancestors as List
      if (parentPageAncestors) {

      void editPage(String pageId, String pageTitle, String pageContent, String pageVersion) {
      //def logx = Logger.getLogger("com.acme.workflows")

      def pageManager = ComponentLocator.getComponent(PageManager)

      def params = [
      id: pageId,
      type: "page",
      title: pageTitle,
      body: [storage:[value:pageContent, representation:"storage"]],
      version: [number:pageVersion]

      def jsonString = new JsonBuilder(params).toString()

      def rest_api_str = "https://HOST/wiki/rest/api/content/${pageId}"
      def http = new HTTPBuilder(rest_api_str)
      try { def json = http.request(, { req ->
      req.addHeader('Authorization', 'Basic ' + 'USER:PASSWORD'.bytes.encodeBase64().toString())
      body = jsonString
      response.success = { resp, jsonData ->
      assert resp.status == 200
      return jsonData
      response.failure = { resp ->
      return resp.status
      } catch(Exception ex) {







Log in or Sign up to comment
AUG Leaders

Atlassian Community Events