Last updatedApr 23, 2019

Creating a new Confluence macro

Redirection Notice

This page will redirect to DOCS:Create a Confluence 'Hello World' Macro.

Level of experienceBEGINNER
Time estimate1:00
Atlassian applicationCONFLUENCE 4.X+

Tutorial overview

This tutorial will show you how to create a macro for Confluence. Macros are treated as plugins in Confluence and other Atlassian applications. The macro you'll build for Confluence generates a list of macros used on a page, and outputs them in a table format. This is a good tutorial if you've never developed an add-on before.

Here's an example how your macro might appear when you're finished:

Macro example table

The following concepts will be covered:

  • The Macro interface.
  • Using the provided API to interact with storage format.
  • The xhtml-macro module descriptor.

About these Instructions

You can use any supported combination of OS and IDE to create this plugin. These instructions were written using Eclipse Indigo on Mac OS X. If you are using another OS or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with Confluence 5.10.1 and Atlassian SDK 6.2.6.

Prerequisite knowledge

You should complete the beginner tutorial, Set up the Atlassian Plugin SDK and Build a Project, before starting this tutorial. You should also already understand basic Java development - classes, interfaces, methods, and how to use the compiler.

Plugin source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you're done, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:

1
$ git clone https://bitbucket.org/atlassian_tutorial/confluence-macro-plugin-tutorial.git

Alternatively, you can download the source code for this plugin project. 

Step 1. Create the plugin project and trim the skeleton

In this step you'll generate stub code for your plugin, and setup the stub code as an Eclipse project. You'll use atlas- commands to automate the plugin creation.  

  1. Open a terminal and navigate to your Eclipse (or equivalent IDE) workspace directory.

  2. Enter the following command to create a Confluence plugin skeleton:

    1
    atlas-create-confluence-plugin
  3. When prompted, enter the following information to identify your plugin:

    group-id

    com.example.plugins.tutorial.confluence

    artifact-id

    tutorial-confluence-macro-demo

    version

    1.0-SNAPSHOT

    package

    com.example.plugins.tutorial.confluence

  4. Confirm your entries when prompted with Y or y.

    Your terminal notifies you of a successful build:

    1
    2
    3
    4
    5
    6
    7
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 1 minute 11 seconds
    [INFO] Finished at: Thu Jul 18 11:30:23 PDT 2013
    [INFO] Final Memory: 82M/217M
    [INFO] ------------------------------------------------------------------------
  5. Change to the tutorial-confluence-macro-demo directory created by the previous step. 

    1
    $ cd tutorial-confluence-macro-demo
  6. Delete the test directories.

    Setting up testing for your macro isn't part of this tutorial. Use the following commands to delete the generated test skeleton:

    1
    2
    $ rm -rf ./src/test/java
    $ rm -rf ./src/test/resources/
  7. Delete the unneeded Java class files.

    You'll build a single class for your macro in later steps.

    1
    rm ./src/main/java/com/example/plugins/tutorial/confluence/*/*.java

Import your project into your IDE

  1.  Make your project available to Eclipse.

    1
    atlas-mvn eclipse:eclipse

    You'll see a successful build message: 

    1
    2
    3
    4
    5
    6
    7
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 54 seconds
    [INFO] Finished at: Tue Jul 16 11:03:59 PDT 2013
    [INFO] Final Memory: 82M/224M
    [INFO] ------------------------------------------------------------------------
  2. Start Eclipse.
    You can open a new terminal window to perform this action.

    1
    2
    $ cd ~/eclipse
    $ ./eclipse
  3. Click File > Import
    Eclipse starts the Import wizard.

  4. Expand the General folder tree to choose Existing Projects into Workspace.
  5. Click Next.
  6. Click Browse and enter the root directory of your workspace.
    Your Atlassian plugin folder should now appear under Projects.
  7. Ensure your plugin checkbox is ticked and click Finish
    Eclipse imports your project, and it's now visible in your Package Explorer view.

Step 2. Create the ExampleMacro class

Your macro leverages the methods and attributes of the com.atlassian.confluence.macro.Macro interface. Here, you'll build the foundation for your class by implementing Macro.

  1. Open Eclipse.
  2. Locate the com.example.plugins.tutorial.confluence package.
    This is located under src/main/java.
  3. Right-click the package and choose New > Class.
  4. Name your class ExampleMacro.
  5. Ensure the superclass is listed as java.lang.Object.
  6. Add com.atlassian.confluence.macro.Macro to import the Macro interface.
    This interface will enable your macro to examine a Confluence space and determine the qualities of other macros used
  7. Tick the checkboxes to include Constructors from superclass and Inherited abstract methods. Leave public static void main(String[] args) unchecked.
    The dialog box should resemble the following:

    New Java Class Eclipse dialogue
  8. Click Finish.
    Eclipse generates stub code for ExampleMacro.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.example.plugins.tutorial.confluence;
    
    import java.util.Map;
    
    import com.atlassian.confluence.content.render.xhtml.ConversionContext;
    import com.atlassian.confluence.macro.Macro;
    import com.atlassian.confluence.macro.MacroExecutionException;
    
    public class ExampleMacro implements Macro {
        public ExampleMacro() {
            // TODO Auto-generated constructor stub
        }
    
        @Override
        public String execute(Map<String, String> arg0, String arg1,
                ConversionContext arg2) throws MacroExecutionException {
            // TODO Auto-generated method stub
            return null;
        }
    
        @Override
        public BodyType getBodyType() {
            // TODO Auto-generated method stub
            return null;
        }
    
        @Override
        public OutputType getOutputType() {
            // TODO Auto-generated method stub      
            return null;
        }
    }

Step 3. Add methods and interfaces to ExampleMacro.java

You've built the stub code for your ExampleMacro class using the Confluence Macro interface. In this step you'll replace some of the stub code to define attributes of your macro.

  1. Open ExampleMacro.java
  2. Annotate this class with @Scanned

    1
    2
    3
    @Scanned
    public class ExampleMacro implements Macro{
    }
  3. Add an import statement for XhtmlContent.

    1
    import com.atlassian.confluence.xhtml.api.XhtmlContent;

    This API method assists with storage content in Confluence. 

  4. Add a constructor for ExampleMacroto take in a value of XhtmlContent.

    1
    2
    3
    4
    5
    6
    7
    private final XhtmlContent xhtmlUtils;
    
    @Autowired
    public ExampleMacro(@ComponentImport XhtmlContent xhtmlUtils) 
    {
        this.xhtmlUtils = xhtmlUtils;   
    }
  5. Modify the getBodyType method to define that your macro's body type is NONE.

    Your macro has no body, so the body type is NONE. There are three available body types for macros: PLAIN_TEXT, RICH_TEXT, and NONE.  

    1
    2
    3
    4
    5
    @Override
    public BodyType getBodyType() 
    {
        return BodyType.NONE;
    }
  6. Define the OutputType of your macro as BLOCK.

    Macros in Confluence can be INLINE or BLOCK formatted. 

    1
    2
    3
    4
    5
    @Override
    public OutputType getOutputType()
    {
        return OutputType.BLOCK;
    }
  7. Save your changes.

Step 4. Define the execute method for ExampleMacro

The execute method is where the meat of your macro is - it controls what your macro actually does. In this step you'll add more interfaces to your ExampleMacro class, and define the execute method in several steps. Your macro will use MacroDefinition and MacroDefinitionHandler to retrieve and parse the bodies of other macros on a given Confluence page. You'll also add the XhtmlException interface to handle cases when your macro is unable to parse XHTML definitions of other macros. 

The final step for ExampleMacro's execution method is to use Java utilities to assemble the information in a table.

  1. Add the following interfaces to ExampleMacro as import statements. 

    1
    2
    3
    4
    5
    6
    import java.util.ArrayList;
    import java.util.List;
    
    import com.atlassian.confluence.content.render.xhtml.XhtmlException;
    import com.atlassian.confluence.xhtml.api.MacroDefinition;
    import com.atlassian.confluence.xhtml.api.MacroDefinitionHandler;
  2. Modify the args for the execute method and define how execute() defines a macro body.

    This enables your macro to examine the body of a given Confluence page, convert the data into a String, and return the storage format of the macros so that your macro can display this information.

    1
    2
    3
    4
    5
    @Override
    public String execute(Map<String, String> parameters, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException
    {
        String body = conversionContext.getEntity().getBodyAsString();
    }
  3. Define your List of macros as an instantiation of a new ArrayList<MacroDefinition>()

    From this step through step 4.5, place your code inside the closing } bracket for the execute() method.

    1
    final List<MacroDefinition> macros = new ArrayList<MacroDefinition>();
  4. Add a call to the handleMacroDefinitions method to take body, conversionContext and a new instance of MacroDefinitionHandler, formatted as a try block.  

    This section of your code will use the XhtmlContent utility to parse the XHTML and add each MacroDefinition to a list. Formatting this code in a try/catch block will account for circumstances when your macro encounters a MacroDefinition it isn't able to parse.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    try
    {
        xhtmlUtils.handleMacroDefinitions(body, conversionContext, new MacroDefinitionHandler()
        {
            public void handle(MacroDefinition macroDefinition)
            {
                macros.add(macroDefinition);
            }
        });
    }
    catch (XhtmlException e)
    {
        throw new MacroExecutionException(e);
    }
  5. Finally, use Java utilities to format the data into a table.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    StringBuilder builder = new StringBuilder();
    builder.append("<p>");
    if (!macros.isEmpty())
    {
        builder.append("<table width=\"50%\">");
        builder.append("<tr><th>Macro Name</th><th>Has Body?</th></tr>");
        for (MacroDefinition defn : macros)
        {
            builder.append("<tr>");
            builder.append("<td>").append(defn.getName()).append("</td><td>").append(defn.hasBody()).append("</td>");
            builder.append("</tr>");
        }
        builder.append("</table>");
    }
    else
    {
        builder.append("You've done built yourself a macro! Nice work.");
    }
    builder.append("</p>");
    
    return builder.toString();
  6. Save and close the file.

Here's what the finalized ExampleMacro class should resemble:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.example.plugins.tutorial.confluence;

import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.content.render.xhtml.XhtmlException;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.confluence.xhtml.api.MacroDefinition;
import com.atlassian.confluence.xhtml.api.MacroDefinitionHandler;
import com.atlassian.confluence.xhtml.api.XhtmlContent;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Scanned
public class ExampleMacro implements Macro{
    private final XhtmlContent xhtmlUtils;

    @Autowired
    public ExampleMacro(@ComponentImport XhtmlContent xhtmlUtils) {
        this.xhtmlUtils = xhtmlUtils;
    }

    public String execute(Map<String, String> params, String bodyContent, ConversionContext conversionContext) throws MacroExecutionException {
        String body = conversionContext.getEntity().getBodyAsString();
        final List<MacroDefinition> macros = new ArrayList<MacroDefinition>();

        try {
            xhtmlUtils.handleMacroDefinitions(body, conversionContext, new MacroDefinitionHandler() {
                public void handle(MacroDefinition macroDefinition) {
                    macros.add(macroDefinition);
                }
            });
        } catch (XhtmlException e) {
            throw new MacroExecutionException(e);
        }

        StringBuilder builder = new StringBuilder();
        builder.append("<p>");
        if (!macros.isEmpty()) {
            builder.append("<table width=\"50%\">");
            builder.append("<tr><th>Macro Name</th><th>Has Body?</th></tr>");
            for (MacroDefinition defn : macros) {
                builder.append("<tr>");
                builder.append("<td>").append(defn.getName()).append("</td><td>").append(defn.hasBody()).append("</td>");
                builder.append("</tr>");
            }
            builder.append("</table>");
        } else {
            builder.append("You've done built yourself a macro! Nice work.");
        }
        builder.append("</p>");

        return builder.toString();
    }

    public BodyType getBodyType() {
        return BodyType.NONE;
    }

    public OutputType getOutputType() {
        return OutputType.BLOCK;
    }
}

Step 5. Modify the atlassian-plugin.xml plugin descriptor

atlassian-plugin.xml describes modules and components to the Atlassian applications - in this case, Confluence. This file bases these dependencies on your pom.xml. The atlassian-plugin.xml file was also generated when you created your plugin skeleton. Here, you'll add an element to reference the ExampleMacro class you built.

  1. Open atlassian-plugin.xml.
    This file should be located under src/main/resources in the Package Explorer view.
  2. Locate the closing tag for </plugin-info>
  3. Add the following code in the line after </plugin-info>. This is a plugin module type. 

    Note that <parameters/> is a blank field. This is because your particular macro doesn't accept input.

    1
    2
    3
    <xhtml-macro name="tutorial-confluence-macro-demo" class="com.example.plugins.tutorial.confluence.ExampleMacro" key="my-macro">
        <parameters/>
    </xhtml-macro>

    Here's what your finished atlassian-plugin.xml file should resemble:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
        <plugin-info>
            <description>${project.description}</description>
            <version>${project.version}</version>
            <vendor name="${project.organization.name}" url="${project.organization.url}" />
            <param name="plugin-icon">images/pluginIcon.png</param>
            <param name="plugin-logo">images/pluginLogo.png</param>
        </plugin-info>
    
        <xhtml-macro name="tutorial-confluence-macro-demo" class="com.example.plugins.tutorial.confluence.ExampleMacro"
                     key="my-macro">
            <parameters/>
        </xhtml-macro>
    
        <!-- add our i18n resource -->
        <resource type="i18n" name="i18n" location="tutorial-confluence-macro-demo"/>
        
        <!-- add our web resources -->
        <web-resource key="tutorial-confluence-macro-demo-resources" name="tutorial-confluence-macro-demo Web Resources">
            <dependency>com.atlassian.auiplugin:ajs</dependency>
            
            <resource type="download" name="tutorial-confluence-macro-demo.css" location="/css/tutorial-confluence-macro-demo.css"/>
            <resource type="download" name="tutorial-confluence-macro-demo.js" location="/js/tutorial-confluence-macro-demo.js"/>
            <resource type="download" name="images/" location="/images"/>
    
            <context>tutorial-confluence-macro-demo</context>
        </web-resource>
        
    </atlassian-plugin>
  4. Save and close the file.

  5. From your project root in terminal, tutorial-confluence-macro-demo, update the Eclipse changes to your project. 

    1
    $ atlas-mvn eclipse:eclipse
  6. Refresh your project in Eclipse.

Step 6. Build, install and run the plugin

You've built your macro, but you haven't yet had a chance to interact with it. Now you'll install your plugin and run Confluence, and put your macro to the test.

  1. Open a terminal window and navigate to the plugin root folder. 

    1
    $ cd tutorial-confluence-macro-demo
  2. Start up Confluence from your project root using the atlas-run command.

    1
    atlas-run

    This command builds your plugin code, starts a Confluence instance, and installs your plugin. This may take a few minutes.

  3. Locate the URL for Confluence.
    Your terminal outputs the location of your local Confluence instance, usually defaulted to http://localhost:1990/confluence.

    1
    2
    3
    [INFO] confluence started successfully in 71s at http://localhost:1990/confluence
    [INFO] Type CTRL-D to shutdown gracefully
    [INFO] Type CTRL-C to exit
  4. Open your browser and navigate to your local Confluence instance.

  5. Login with adminadmin.
  6. Click Create and follow the prompts to create a new page.
  7. Add at least 2 macros to your page. 
    You can add macros by typing a single curly brace ({) and choosing from the suggested list. 
    Alternatively you can click Insert > Other Macros to open the macro browser.
  8. Next, go to Insert > Other Macros and add your Tutorial confluence macro demo macro.
  9. Save the page.
  10. Confirm your macro displays the names and body statuses of the other macros used on the page.
    Finished product
    You can experiment - add, remove, modify your macros, and re-save the page. Confirm your macro stays up to date.

Step 7. Extra credit: Add an icon in the macro browser

When you created the project, two images were automatically downloaded to serve as the logo and icon: pluginIcon.png and pluginLogo.png. These files are located under src/main/resources/images for your project. In this step, you'll add your own custom images to your macro. This image will be shown in the Confluence macro browser:

Macro browser with icon
  1. Locate an image of your liking.
  2. Resize the image to 80 x 80 pixels.
  3. Save the image under src/main/resources/images in a png format.
  4. In atlassian-plugin.xml, add a parameter to specify the location of the icon image using icon="".

    1
    2
    3
    <xhtml-macro name="tutorial-confluence-macro-demo" class="com.example.plugins.tutorial.confluence.ExampleMacro" icon="/download/resources/${project.groupId}.${project.artifactId}/images/browserLogo.png"  key="my-macro">
        <parameters/>
    </xhtml-macro>
  5. Relocate the resource for /images from <web-resource> to directly below the i18n resources.
    Your resources should appear as following:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <resource type="i18n" name="i18n" location="tutorial-confluence-macro-demo"/>
    <resource type="download" name="images/" location="/images"/>
    
    <!-- add our web resources -->
    <web-resource key="tutorial-confluence-macro-demo-resources" name="tutorial-confluence-macro-demo Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
    
        <resource type="download" name="tutorial-confluence-macro-demo.css" location="/css/tutorial-confluence-macro-demo.css"/>
        <resource type="download" name="tutorial-confluence-macro-demo.js" location="/js/tutorial-confluence-macro-demo.js"/>
1
2
3
    <context>tutorial-confluence-macro-demo</context>
</web-resource>
```
  1. Save and close the file.
  2. Rebuild your plugin - QuickReload will automatically reinstall your plugin into the running instance.
  3. Search for your macro in the macro browser while editing a page.
    Check out your awesome icon!
    Close-up of macro browser icon
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">
    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
        <param name="plugin-icon">images/pluginIcon.png</param>
        <param name="plugin-logo">images/pluginLogo.png</param>
    </plugin-info>

    <!-- add our i18n resource -->
    <resource type="i18n" name="i18n" location="tutorial-confluence-macro-demo"/>
    <resource type="download" name="images/" location="/images"/>
    
    <!-- add our web resources -->
    <web-resource key="tutorial-confluence-macro-demo-resources" name="tutorial-confluence-macro-demo Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        
        <resource type="download" name="tutorial-confluence-macro-demo.css" location="/css/tutorial-confluence-macro-demo.css"/>
        <resource type="download" name="tutorial-confluence-macro-demo.js" location="/js/tutorial-confluence-macro-demo.js"/>


        <context>tutorial-confluence-macro-demo</context>
    </web-resource>
   
    <xhtml-macro name="tutorial-confluence-macro-demo" class="com.example.plugins.tutorial.confluence.ExampleMacro" icon="/download/resources/${project.groupId}.${project.artifactId}/images/browserLogo.png" key="my-macro">
        <parameters/>
    </xhtml-macro>
    
    <!-- import from the product container -->
    <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
    
</atlassian-plugin>