Products
Services
Blog

Reuse of Attachment in Confluence

Roma Bubyakin

Roma Bubyakin

Jul 17, 2025

Read 8 min

Article

Topics covered: Confluence Rest API, Confluence Storage Format, User Parameters

Intro

The Problem

Uploading the image to 100 pages… Huh, tough job, but the task is done! (tick)

Designers changed the logo which used on 100 pages 🤯

Or imagine you have a dozen UI screenshots all around the documentation, and the product design has been changed. How to update it in all places at once?

Who am I?

Hi, my name is Roma. I’m the founder of Wombats Corp.

I will help you to solve some REAL problems in Confluence.


Request

Christia K_ has asked on the community.atlassian.com

how to link to often used images without uploading them on every single page?

We have a lot of pages with the same image (logo of a customer, screenshots etc.)

How do I  incorporate them on my < 100 pages without uploading them on every single page? 

When changing the logo, I could change it on 1 single instance on the server version / old editor without uploading it again on < 100 pages?

The Real Problem!

Let’s take a look at replays:

  • Marc from Devoteam suggested using page templates, but it does not make work easier.

  • Barbara Szczesniak suggested Excerpt Include macros for each image. It might work partially. But not for all use cases. Also, she mentioned an open suggestion for years CONFCLOUD-65344

Maybe User Macro can deal with it?


Logo Library macro

Let’s do the macro for multiple logos of our partners.

Create the simplest image macro

It will be a static, inline macro, without a body.

image-20250610-163801.png

So, in the admin panel new User Macro was created, and an additional Confluence page will be needed to test the result.

To start, let's insert an image. We'll do this by using a macro, which gives us more control than a direct upload.
The simplest version. With an image from seeklogo.com.
As we create a static macro, we have to stick to the Confluence Storage Format.

The template for rendering an image:

<ac:image>
  <ri:url ri:value="https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png"/>
</ac:image>

Open a test page, full reload (Ctrl/Cmd + R) to pick up the newly created macro, and add /logo macro to the page.

image-20250610-164756.png

Adding User Parameter

It works, but the image is too big. Adding a parametrized size will help in future macro usage.
Image sizing is done via ac:height and ac:width attributes. However, as we talk about the logo, we will use only one parameter, and another will be adopted proportionally. It will simplify sizing and does not distort proportions.

Click on the + Add user parameter under the Variables block.

image-20250613-141520.png

It will be a Size parameter with type of Int:

image-20250610-165506.png

The parameter is not required, and if the user skips it logo will be in its original size.

Updated template:

<ac:image ac:height="$parameters['size']">
  <ri:url ri:value="https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png"/>
</ac:image>

Now it’s better:

image-20250612-193106.png

Adding a list of images

Kind of working, however, there is a list of partners, and macro should support each.

The list of partners is enumeration, and below is a User Parameter for it.

image-20250612-193755.png

It must be required, as there is no logical default option.

For mapping partner name with logo URL map from Apache Velocity is used.
It looks more elegant than having a bunch of ifelses.

Variable $images is a map with key-value pairs.
Variable $image is an actual URL that we need.
Now the image URL is parameterized instead of hardcoded.

As a bonus ac:alt and ac:title attributes will be used to follow best practices of image usage.

#set ($images = {
  "SAP":        "https://images.seeklogo.com/logo-png/18/1/sap-logo-png_seeklogo-180805.png",
  "McDonalds":  "https://images.seeklogo.com/logo-png/30/1/mcdonalds-logo-png_seeklogo-301644.png",
  "NASA":       "https://images.seeklogo.com/logo-png/9/1/nasa-logo-png_seeklogo-97034.png",
  "Lego":       "https://images.seeklogo.com/logo-png/8/1/lego-logo-png_seeklogo-83157.png",
  "Volkswagen": "https://images.seeklogo.com/logo-png/15/1/volkswagen-logo-png_seeklogo-150537.png",
  "AMD":        "https://images.seeklogo.com/logo-png/0/1/amd-logo-png_seeklogo-7775.png?v=1954738441079066064"
})

#set ($image = $images.get($parameters["partner"]))
<ac:image ac:height="$parameters['size']"
  ac:alt="$parameters['partner'] logo"
  ac:title="$parameters['partner'] logo">
  <ri:url ri:value="$image"/>
</ac:image>

With an output type of inline, multiple logos can be lined up one by one, as shown in the screenshot below:

image-20250612-194958.png

The end (?)

Seems that the macro has been completed and uploaded to our macro examples.

However, it appears that Christia K_ wants a bit more:

…for a Technical User Manual you need a lot of screenshots of your software, some are used multiple times such as menubar, accept button etc. But others are only used once.
So it is very helpful to have the linked pictured because you only update your screenshot once and it will be updated on all different places, without the need to search them all.

Not a problem! Let’s do this one also 😎

Image from Attachments

Required changes

For this one, we have to make some changes.
The map of URLs is no longer needed.

Instead of the Partner parameter, Attachment is needed.
It will be a type of String. As the Attachment type is limited by the current space and is less universal.

image-20250612-210621.png

To make the code cleaner searchName is assigned to the name variable:

#set ( $name = $parameters["searchName"] )

Requesting Confluence

Now that we have an attachment name, the next step is to get attachment’s URL programmatically.
Get Attachments endpoint will do the job:

  • limit=1 — will bring only one file

  • sort=-modified-date — will bring the last updated attachment to the top

  • status=current — it will filter out archived/deleted/drafted attachments

  • filename=$name — file name from User Parameter

#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)
{
  "results": [
    {
      "id": "<string>",
      "status": "<current | draft | archived | historical | trashed | deleted | any>",
      "title": "<string>",
      "createdAt": "<string>",
      "pageId": "<string>",
      "downloadLink": "<string>",
      ...
    }
  ],
  "_links": {
    "next": "<string>",
    "base": "<string>"
  }
}

Object structure has a root element results. That’s why, after getting attachments first action is to access the results, which will go to the $results variable.

As the request already has limit=1 parameter, $results will have only one element with index 0.

And we are looking for the downloadLink attribute. downloadLink is a relative link, to make it valid, prefix it with $baseUrl.

Working macro

Let’s put everything together, and here is the template:

#set ( $name = $parameters["searchName"] )

## Make the search
#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)

## Add an image in Storage Format
#set ( $url = "$baseUrl$results[0].downloadLink" )
<ac:image 
  ac:alt="$name"
  ac:title="$name"
  ac:height="$parameters['size']">
  <ri:url ri:value="$url"/>
</ac:image>

It works 🔥

image-20250612-213654.png

Updating the attachment with painting.png name will update the image on this page automatically.

Final improvements

Based on user input, two edge cases are possible:

  1. It’s not an image.

  2. Search result is empty as there is no attachment with the given name.

To improve user experience, warning blocks have been added for edge cases.
It’s notable that only inline CSS styles are applicable to the Storage Format:

## Check for incompitable filetype
#set ( $name = $parameters["searchName"] )
#if(!$StringUtils.endsWithAny($name.toLowerCase(), ".png", ".jpg", "jpeg", ".gif" ))
  <div style="background-color: #fef7c8; padding: 10px; border-radius: 3px;">
    <div style="display: inline-block; width: 95%;">
      <div style="margin: 1px 0 5px 0;">
        <ac:emoticon ac:name="warning" ac:width="30px" /> Image [$name] must be PNG, JPG, or GIF format. Update the Attachment parameter
      </div>
    </div>
  </div>
  #stop
#end

## Make the search
#set ( $results = $ConfluenceManager.get("/wiki/api/v2/attachments?limit=1&sort=-modified-date&status=current&filename=$name").results)

## Show a warning if nothing found
#if(!$results)
  <div style="background-color: #fef7c8; padding: 10px; border-radius: 3px;">
    <div style="display: inline-block; width: 95%;">
      <div style="margin: 1px 0 5px 0;">
        <ac:emoticon ac:name="warning" ac:width="30px" /> No [$name] attachments has been found (Hint: <i>search is case-sensitive</i>)
      </div>
    </div>
  </div>
  #stop
#end

## Add an image in Storage Format
#set ( $url = "$baseUrl$results[0].downloadLink" )
<ac:image 
  ac:alt="$name"
  ac:title="$name"
  ac:height="$parameters['size']">
  <ri:url ri:value="$url"/>
</ac:image>

Now it is even better and uploaded to the Library: https://wombatscorp.com/user-macros-examples/image-from-attachments-confluence-macro


The REAL problem has been solved with User Macro for Confluence Cloud (wink)

Got Questions?

Fill out the form, and we'll guide you through every step of the way