Last updated Jul 12, 2024

Creating a dynamic content macro

This tutorial teaches you how to create a simple dynamic content macro module that displays a random image of a cute dog. We're using a dynamic content macro because our macro won't block the rest of the page from loading while it fetches the image.

By the end of this tutorial, you will:

  • create a new app
  • add the dynamicContentMacros module to your app
  • create a route handler that makes the macro functional
  • build a view to display the macro content in a Confluence page

Let's get started!

Before you begin

Ensure you have installed all the tools you need for Confluence Connect app development by Getting set up with Atlassian Connect Express (ACE):

  1. Get a cloud development site.
  2. Enable development mode in your site.
  3. Install ACE.

Create a new app

The first step is to use atlas-connect to create the app framework: a directory called macro-tutorial that contains all the basic components of a Connect app.

  1. From the command line, go into a directory where you'd like to work, and type:
    atlas-connect new macro-tutorial
  2. Select Confluence in the menu.
  3. When the command finishes, go into the macro-tutorial directory and run npm install to install any dependencies for the app.
  4. add the credentials.json file that gives your app permission to work with your Confluence Cloud development site. Copy the one you created during the Getting started tutorial. You can replace or delete the existing credentials.json.sample file.

Take a look around the macro-tutorial directory. We'll be adding:

  • a module in the atlassian-connect.json file
  • a route handler in the routes directory
  • a view in the views directory

Add the module

You add a module by declaring it in your app's atlassian-connect.json file, also known as the app descriptor.

  • Add a dynamicContentMacros object by pasting the following code over the existing modules block:

      "modules": {
          "dynamicContentMacros": [
                  "url": "/dog",
                  "description": {
                    "value": "Pictures of dogs"
                  "name": {
                    "value": "Dog picture"
                  "key": "pictures-of-dogs"

Take note of the following fields:

  • url: This is the endpoint your app will use to handle requests for the module. The route handler we'll add in the next section will handle requests to this URL.
  • name.value: This is the name that Confluence Cloud shows for your macro
  • key: This is a unique identifier for your macro

The key must be unique across all macros in all apps, so it's normally a good idea to add your app key as a prefix. We'll skip that step for now.

Add a route handler

A route handler is the code that runs when your module's endpoint is called.

  • In routes/index.js, add a new app.get() method under the // Add additional route handlers here… comment:

      // Add additional route handlers here...
      app.get('/dog', addon.authenticate(), async (req, res) => {
          const title = 'Random dog';
          res.render('dog.hbs', {title});

For now, this code is very simple. All it does is render a view called dog, passing it a title which is set to "Random dog."

Add a view

The route handler won't do anything without the view.

  • To create a simple view, add a file called dog.hbs in the views directory, with the following contents:

    {{!< layout}}
    <div id="dogImage">

Notice the following:

  • The filename dog.hbs corresponds to the name dog in the res.render statement in the route handler. This is how the route handler finds the view.
  • The first line, {{!< layout}}, makes the new view include everything from layout.hbs. This is how you make sure your view includes all the JavaScript you'll need when building a full-featured app.
  • The {{title}} is a placeholder to be filled in by the title parameter in the res.render statement.

Test the app

The macro isn't finished yet, but let's test what we've got so far.

  1. From the command line, make sure you're in the macro-tutorial directory, then type npm start to start the app.
  2. Check that the app is running by going to in a browser and looking for POST /installed.
  3. Edit a page in your Confluence Cloud developer site.
  4. Type /Dog or select Dog picture (Pictures of dogs) from the Macro browser.
  5. Publish the page.

You should see Random Dog on your page. Now you're ready to make the app do more.

Make it do more

We'll use the fetch package to get pictures of dogs.

We'll make some changes to the route handler to get a random dog picture, and a change to the view so that we can display the picture.

  1. Stop the app: from the command line where you ran the app, type Control-C.
  2. To install the fetch package, type the following at the command line:
    npm i node-fetch --save
  3. At the top of index.js, add the following line:
    const fetch = require("node-fetch");
  4. Change the /dog route handler to look like this:
     app.get('/dog', addon.authenticate(), async (req, res) => {
         const response = await fetch('');
         if(!response.ok) {
             const textContent = response.text();
             console.log(`error while getting random dog picture: ${textContent}`);
         const title = 'Random dog';
         const jsonContent = await response.json();
         const imageUrl = jsonContent.message;
         res.render('dog', {title, imageUrl});
    This code uses the fetch package to make a request, then parses the returned JSON response to obtain the URL for an image. We'll pass this URL to the view for rendering.
  5. Under the {{title}} in dog.hbs, add the following line:
    <img src="{{imageUrl}}" alt="{{title}}"/>
    The view should now look like this:
    {{!< layout}}
    <div id="dogImage">
       <img src="{{imageUrl}}" alt="{{title}}"/>
  6. Type npm start to test your app. You should see a new dog picture every time you reload the page.


Adding a macro (or any module) is as easy as 1, 2, 3:

  1. Declare the module in the app descriptor atlassian-connect.json.
  2. Add a route handler that defines the module's behavior.
  3. Create a view to display the module's content if needed.

Now that you know the basic pattern, you can try some of the other tutorials, and experiment with modules such as the content byline item.

Rate this page: