Creating an Admin Configuration Form


This tutorial describes how to create plugins that work across Atlassian products. It does so by creating a plugin that works with RefApp 2.12.0.

Level of experience:

This is an advanced tutorial. You should have completed at least one intermediate tutorial before working through this tutorial. See the list of tutorials in DAC.

Time estimate:

It should take you approximately 2 hours to complete this tutorial.

On this page:

Overview of the tutorial

Along the way, this tutorial introduces these concepts:

Your completed plugin will consist of:

  • Java classes encapsulating the plugin logic.
  • Resources for display of the plugin user interface (UI).
  • A plugin descriptor (XML file) to enable the plugin module in the Atlassian application.

Prerequisite knowledge

To complete this tutorial, you need to know the following: 

  • The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
  • How to create an Atlassian plugin project using the Atlassian Plugin SDK. 

Plugin source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you have finished, 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:

git clone

Alternatively, you can download the source using the get source option here:

About these Instructions

You can use any supported combination of OS and IDE to construct this plugin. These instructions were written using Ubuntu 10.04. If you are using another OS or an IDE, you should use the equivalent operations for your specific environment.

Step 1. Create the plugin project

In this step, you use an atlas- command to generate stub code for your plugin. The atlas- commands are part of the Atlassian Plugin SDK, and automate much of the work of plugin development for you.

  1. Open a terminal and navigate to the directory where you want to create your tutorial project directory.
  2. Enter the following command:

  3. When prompted, enter the following information to identify your plugin:









  4. Confirm your entries when prompted.

The command creates a directory named xproduct-admin-ui-plugin, which contains all your initial project files. This tutorial refers to this directory as your project home. Unless otherwise indicated, all directory paths are relative to the project home.

Step 2. Review and tweak the generated project

It's a good idea to familiarise yourself with the generated project files. In this section, we'll check a version value and tweak the generated files. We'll also start up the RefApp and have a look around.

Add plugin metadata to the POM

The POM (Project Object Model definition file) is located at the root of your project and declares the project dependencies and other information.

Add some metadata about your plugin and your company or organisation.

  1. Edit the pom.xml file in the root folder of your plugin.
  2. Add your company or organisation name and your website to the organization element:

  3. Update the description element:

  4. Save the file.

Verify your host application version

As mentioned in the overview, the host application for the plugin you are creating in this tutorial is the RefApp. When you generated the stub files, a default version specification was included in your pom.xml file for the RefApp. Take a moment and examine the dependency:

  1. If it's not already open, open the pom.xml file.
  2. Scroll to the bottom of the file.
  3. Find the properties element.
    This section lists the version of the RefApp and also the version of the atlas- commands you are running.
  4. Verify that the RefApp version is the one you want. If you are not sure which version you need, check the version table at the top of this tutorial.
  5. Save and close the pom.xml file.

Review the generated plugin descriptor

Your stub code contains a plugin descriptor file atlassian-plugin.xml. This is an XML file that identifies the plugin to the host application (HOSTAPP) and defines the required plugin functionality. In a text editor or IDE (integrated development environment, such as Eclipse or IDEA) open the descriptor file located in your project under src/main/resources. It should look like this:

Start and view the RefApp

Although you haven't modified the generated code yet, try starting the RefApp application now. It's always a good idea to check your work periodically by making sure the application compiles and starts up.

  1. At the command line, change directory to your plugin project home (the xproduct-admin-ui-plugin directory created earlier).

  2. Enter the atlas-run command.

This triggers the RefApp startup process. When finished, you should see output similar to:

Notice the URL for the RefApp. In this case, it is:


Open the RefApp in a browser and have a look around, although there isn't much to see yet.  

Step 3. Add the web form servlet

Let's add a servlet to the RefApp that presents a webform in the RefApp UI. The form won't do anything yet, but it gives us something to add to as we go.

Create the servlet

Let's add a servlet to the project.

  1. Change directories to
  2. Make a new directory at that location, named xproductadminui.
  3. In the new directory, create a new file named with the following contents:

  4. Save and close the file.

Optionally, run the application again (using the atlas-run command from the project home), just to make sure everything compiles and starts up okay.

Add a credential check

Our web application first checks whether the user is logged in. If not, it redirects the user to the login page. We use the SAL User Manager feature to make sure that the current user is an administrator, so we need to add this dependency to our project file.

Open pom.xml and add the following dependency element alongside the other dependencies elements, immediately after the packaging declaration.

With SAL added as a build dependency, we can import SAL and use it in our servlet.

  1. Navigate to the directory:
  2. Open for editing. 
  3. Add the following import statements alongside the existing statements:

  4. Replace the entire AdminServlet class declaration with the following:

In our doGet method, we check whether the user is logged in (that is, username is not null) and that the user is an administrator, thanks to the UserManager class. If either are false, we redirect the user to the application login page by calling the redirectToLogin method, which we haven't implemented yet. 

Add the following two methods, redirectToLogin and getUri methods to the AdminServlet class immediately after doGet:

Notice that the getUri method returns the current URI to the LoginUriProvider so that the user is redirected back to our admin UI when they are properly authenticated.

Render the form with the Atlassian Template Renderer

Now let's get to rendering! As mentioned in the Overview, we can use the Atlassian Template Renderer for that. Once again, before we can write the code, we need to tell Maven that we'll be using it.

Find the dependencies section in the pom.xml file and add the following dependency

In, add the following import statement to the existing ones:

import com.atlassian.templaterenderer.TemplateRenderer;

We need to instantiate the renderer object in the constructor and call it in the doGet method. Add the renderer variable declaration to the existing ones and replace the entire AdminServlet constructor and doGet method with the following:

We've added a dependency on a TemplateRenderer to our constructor. This is the renderer we use to display our form. In the doGet method, after applying our authorization check, we set the response content type to text/html and then make a call to the templateRenderer, telling it to render our admin.vm template. We will create the template shortly.

Modify the Atlassian plugin descriptor

Before we go any further, let's tell the plugin system about our servlet.

Open atlassian-plugin.xml for editing and add the following component import and servlet declaration statements. You can add it anywhere within the atlassian-plugin element, such as after the existing servlet declaration.

With that done, let's test the login redirect logic we've added. Start the RefApp by running the atlas-run command again and go to:


A login prompt should appear. Log in using the credentials admin/admin. Once logged in, you won't see much except a big exception saying that the admin.vm file couldn't be found. Let's create it.

Step 4. Create the Velocity template

A Velocity template determines the HTML presentation produced by our application.

Create a Velocity template file named admin.vm in the src/main/resources directory. To start, we keep it simple. Add this to the file:

In your browser, refresh the admin page, http://localhost:5990/refapp/plugins/servlet/xproduct/admin

You should see something like this:

It's a start. But it's also, well, ugly. It's missing application template features, such as the Atlassian logo and menus. Let's fix that.

Add some decoration

To style the page, open admin.vm for editing again and add this meta tag. Add it to the head element, after the existing title element.

This tells the application that it needs to use the admin decorator around the body of this page.

Now the page looks like something the application would actually display.

Now we're getting somewhere. But the form itself still doesn't look so good. AUI to the rescue!

Style it

First we need to include AUI in our header. Add the following line after the meta tag in the admin.vm file.

What is $webResourceManager and where does it come from? It's the web resource manager from the plugin system used to manage CSS, JavaScript and other resources that are usually linked at the top of pages using <script>. Calling requireResource will include all the resources for the web-resource module along with its dependencies.

But where did the WebResourceManager come from? It was automatically added to the Velocity context by the TemplateRenderer. It makes a few things that are commonly useful available, like the WebResourceManager. We'll see another one that is included automatically below, when we get to internationalizing our template.

Next, we improve the appearance of the form by adding class attributes.

The changes were small. We added class="aui" to the form element, class="text" to each of our text input boxes, class="button" to our submit button, and class="field-group" to our div tags. And for such little work we get:

A nice looking form in any Atlassian application, and consistent with the application look and feel!

I18n it

We can also add internationalization support to our user interface pretty easily.

First, add a resource element specifying our i18n properties file to atlassian-plugin.xml

Next, create the properties file that holds the label text for the form. Under the src/main/resources/ directory, create the following directory path and file:


In the file, add this text:

These properties define the labels that will appear in our UI. For example, the label for the time field is 'Time'.

Next, replace the hard-coded text values in the admin.vm template with lookups for internationalized text.

$i18n is an instance of SAL's I18nResolver, which comes automatically with TemplateRenderer, just like the WebResourceManager. We use it to resolve our message keys to actual text.

Feel free to experiment with the labels in the properties file. To localize an application for an actual deployment, the only thing left to do would be to get the properties file translated.

Now we have our basic UI. It's time to make it do something!

Step 5. Make the JavaScript and REST happen

Our form is pretty simple. You can't post anything with it yet. To be able to post and display data in the form, we'll use JavaScript to make AJAX requests to a REST resource. The resource returns the data as JSON, which our form displays.

Add our JavaScript to the template

The recommended way to add JavaScript to pages is to create a web-resource module in your atlassian-plugin.xml file. Using this method, you can also specify dependencies on other web-resource modules, like AUI.

Add this to atlassian-plugin.xml:

Now edit the admin.vm template. Replace the existing $webResourceManager.requireResource call with:

Now we can write some JavaScript!

JavaScript for GETting the configuration

Populating our form is pretty easy. AUI supports jQuery, which we use to communicate with our REST resources. Start out by making a GET request to set the values of the input fields.

Create a file named admin.js in src/main/resources/, and add the following JavaScript code to the file:

This JavaScript creates a URL by combining the root context path of our application from AJS.contextPath() (which might be something like "/jira", "/stash", or even an empty string, "") with the hard-coded relative URL of our REST resource. Then it queries the server to find the current config. Once done, it populates the name and time elements with our data.

Next we write the REST resource that serves the data.

Serve the configuration from a REST resource

The JavaScript we've added makes a request to a configuration resource for populating our form. Since it doesn't exist yet, we need to create this resource. But before we write the code for that, we need to add a few more dependencies in Maven. The Atlassian REST module uses Jersey, an implementation of the JAX-RS API. We'll add a dependency for this JAX-RS API. We'll also use JAXB to serialize our configuration object for us, to avoid having to manually construct JSON, so we need to add a dependency for that as well.

Open the pom.xml file and add these dependency elements to the dependencies section:

Now we can write our REST resource class.

Retrieve the configuration with GET

Again, start with a simple class and build it up from there.

Create a new file named in your project home directory:


Add the following:

This contains all the import statements we need, as well as a simple class constructor.

To populate the application web form, we issue a GET request to the resource that returns the data as JSON. JAXB serializes our configuration object for us, making life a bit easier.

First, add an inner class that encapsulates our configuration data.

Here we've created a class that will encapsulate a few properties, a String and an int. The @XmlRootElement and @XmlElement annotations are JAXB annotations. The @XmlRootElement annotation declares that instances of this type can be serialized to XML, and with a little magic in the REST module, to JSON as well. The @XmlElement annotations declare that the attributes should be treated as XML elements or JSON object properties.

If we were to serialize an instance of this class to XML, it would look like this:

And in JSON, like this:

Now let's implement the GET method, which returns a Response with an instance of the Config type we've just created as the entity.

This method first performs our now familiar user check.

We then construct a Response with an entity that is returned from a call to a funky transactionTemplate.execute method, providing it with an instance of TransactionCallback. If you've used Spring's TransactionTemplate, this should look familiar to you. For everyone spoiled by AOP, the summary is that we are accessing data that is coming from a database—well, it's not in Fisheye or Crucible, but work with me here!

To ensure that reads and writes don't clash and give us inconsistent data, we need to protect ourselves any time we access PluginSettings data. The TransactionTemplate frees us from needing to know the application-specific transaction creation and usage semantics. If we didn't use the TransactionTemplate, we'd need code something like this:

But TransactionTemplate takes care of managing the transaction for us, by calling TransactionCallback to perform the real work. The return value of the TransactionTemplate.execute method is the return value of calling TransactionCallback.doInTransaction.

Inside our TransactionCallback.doInTransaction method, we use the PluginSettingsFactory to create a reference to the global, application-wide settings data. We need to use the global settings object because we're configuring a plugin. If we were storing data associated with a project (in JIRA, Bamboo, Fisheye or Crucible) or a space (in Confluence), we'd instead use the PluginSettingsFactory.createSettingsForKey method, where the key is the project or space the configuration data should be associated with.

Once we have a reference to a PluginSettings object, things are pretty easy. PluginSettings can be thought of as a persistent map. Just use get to retrieve data and put to add or update values.

Namespace your keys!

Because this is global application configuration data, it is important that you do some kind of namespacing with your keys. Whether you use your plugin key, a class name or something else entirely is up to you. Just make sure it is unique enough that conflicts with other configuration data won't occur.

Step 6. Updating the configuration

Now that our form can be populated with our current configuration, we want to allow the admin user to update the configuration.

JavaScript for PUTting an updated configuration

Add the following function to admin.js, after the populateForm function definition but before the call to populateForm.

This is the function we call to update the server configuration. It's very minimal; we haven't included error checking and we really should be escaping quotes and other special characters when creating our data, but that is left as an exercise for the reader.

Now, we just need to hook that function up to our form. After the call to populateForm() in admin.js add:

Now, whenever the form is submitted, instead of the usual, default action of POSTing the form data, our updateConfig() method is called and our updated configuration is submitted as a PUT request to the configuration resource.

Update the configuration with PUT

To handle the PUT requests from the client, add this method to the file you created earlier:

The @Consumes annotation declares that we expect a request with a application/json body. We expect JSON in a form that matches the serialized form of the Config object we created earlier, so that the REST module can deserialize to a Config object, which it then passes to the put method.

Next, we do the authentication and authorization check to make sure no one can modify the configuration except admin users. Then, we wrap our modifications in TransactionCallback, which we pass to the transactionTemplate, just like we did when we were retrieving the configuration. Since PluginSettings is just a persistent map, we just use the put method to put the new configuration values into it and we're done.

Finally, we return 204 No Content, which tells the client that the update succeeded and there's nothing left to be said.

Wire things together

Finally, we need to add a few items to the atlassian-plugin.xml file. We have used a few new services from SAL, which we need to import. We also need to add the rest module, so that the plugin system knows about the REST resource and where to find it.

Add the following lines to atlassian-plugin.xml:

Step 7. Add menu items

Fortunately, the application attribute will come to our rescue (mostly)!

To add a link in the 'Global Settings' menu in JIRA's administration area, add the following web-item element.

To add a link in the 'Global Settings' menu in Fisheye's administration area, add the following web-item element.

Looking carefully, you'll notice that the label key attribute isn't really a key. This is because Fisheye doesn't do any internationalization yet, so the value of the key attribute will be the text that is displayed.

To add a link in the 'Plugins' menu in Bamboo's administration area, add the following web-item element.

Bamboo has the same limitation as Fisheye, so again the label element's key attribute is the textual value we want to display.

To add a link in the 'Configuration' menu in Confluence's administration area, add the following web-item element.

Astute readers will notice the suspicious lack of the application tag on that web-item element. Well, in Confluence, if you add the application attribute, your web-item won't show up at all. We'll leave it off here, which is safe because none of the other Atlassian applications have a system.admin/configuration section—at least, at the time of this writing they don't.

To add a link in the 'General menu' in the RefApp's administration area.

That's it! We're done! Let's run it!

Step 8. Build, install and run the plugin

Start your RefApp application again by entering the atlas-run command, or use a command below to try the application in another product. After the application starts, navigate to the plugin page by appending the plugin path to the base application URL. For example, for JIRA the path would be:


The startup commands are:

  • For JIRA

  • For Confluence

  • For Fisheye

  • For Bamboo

Congratulations, that's it

Have a chocolate!


About the Atlassian RefApp

SAL UserManager


AUI class attributes

Was this page helpful?
Powered by Confluence and Scroll Viewport