About this sample add-on

The Atlassian Connect User Browser is a sample Atlassian Connect add-on that add QR-codes for users' vCards (using the Google Chart API) to the JIRA interface. In the add-on, we accomplish this by creating a user picker using Select2 library, an open-source select control replacement, styled to match ADG guidelines.

The results look like this:

QR Codes for users

Before you start

If you're not familiar with Atlassian Connect development, you should start with the basics. The Hello World tutorial is a very good place to get started.

To try out the add-on in JIRA, you'll need a web server to act as the add-on host. If you have PHP or Python, you can run php -S localhost:9000 or python -m SimpleHTTPServer 9000 from your project directory to start a simple web server serving your add-on files.

In this case, our add-on files consist of an add-on descriptor (atlassian-plugin.xml), the index.html file that contains all of our logic and presentation code, and a few other resources, as we'll describe below.

Our resource files assume that the add-on is running on http://localhost:9000/ and we have a JIRA instance running on http://localhost:2990/jira. If your environment differs, you'll need to adjust settings in the files accordingly.

We'll look at the parts of our add-on one-by-one. By following these steps, you can get a better understanding of how the add-on works, and use it as a building block for creating your own add-ons for Atlassian Connect.

Step 1. Create the add-on descriptor

Every Atlassian Connect add-on needs a descriptor file called atlassian-plugin.xml. In our case the descriptor is very simple and only defines a general page used to display user vCards.

<?xml version="1.0" ?>
<atlassian-plugin key="ac_user_browser" name="Atlassian Connect User
       Browser" plugins-version="2">

        <description>Simple user picker implementation using select2 library</description>
        <vendor name="Atlassian" url="https://atlassian.com/" />
            <!-- Permissions needed to implement a user picker -->
    <!-- Define where the plugin is hosted -->
    <remote-plugin-container key="container" display-url="http://localhost:9000/" />
    <!-- Define a general page available through header navigation, the url is relative to remote plugin container. -->
    <general-page key="general" name="User browser" url="/" />

Since we want to create a user picker, we'll need to have permissions to read users and groups on the target JIRA instance. We also need a page to see the results. It will be accessible in JIRA's header menu with the name we specified in the descriptor, "User browser."

Step 2. Add resources

We will need the newest version of the AUI flatpack page. We also need standard Atlassian Connect resources to be able to implement cross-domain communication between our plugin and JIRA. We will use the ADG-styled select2 control available with flatpack to implement the user picker.

<!DOCTYPE html>
    <title>Atlassian Connect Select2 user picker</title>
    <!-- AC resources -->
    <link href="http://localhost:2990/jira/atlassian-connect/all.css" rel="stylesheet" type="text/css">
    <script src="http://localhost:2990/jira/atlassian-connect/all.js" type="text/javascript"></script>
    <!-- AUI flatpack -->
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/css/aui.css" media="all">
    <!--[if lt IE 9]><link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/css/aui-ie.css" media="all"><![endif]-->
    <!--[if IE 9]><link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/css/aui-ie9.css" media="all"><![endif]-->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/js/aui.js"></script>
    <!--[if lt IE 9]><script src="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/js/aui-ie.js"></script><![endif]-->
    <script src="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/js/aui-soy.js"></script>
    <!-- AUI experimental components including select2 -->
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/css/aui-experimental.css" media="all">
    <script src="//cdnjs.cloudflare.com/ajax/libs/aui/5.3-m3/js/aui-experimental.js"></script>
    <style type="text/css">
        body {
            overflow: hidden;
        .aui-page-panel-content > form {
            min-height: 300px;
    <script type="text/javascript">
        /* JavaScript code of the add-on */

Step 3. Define the view

Let's create a simple page with a form containing a user picker, and a place to display QR codes with the user's vCards.

To do that we'll use standard AUI page layout, with a form containing a user picker and a place to display QR-code vCards.

<!-- head... -->
    <section id="content" role="main">
        <header class="aui-page-header">
            <div class="aui-page-header">
                <div class="aui-page-header-inner">
                    <h1>User browser</h1>
        <div class="aui-page-panel">
            <div class="aui-page-panel-inner">
                <section class="aui-page-panel-content">
                    <form action="" method="get" class="aui">
                        <div class="field-group aui-select2-userpicker">
                            <label for="my-user-picker">Users</label>
                            <input type="text" id="my-user-picker" class="text long-field" data-placeholder="Select user">
                        <div class="field-group hidden" id="user-details">
                            <label for="my-user-picker">QR-code vCards</label>

Step 4. Implement a user picker

Now we need to implement the user picker. To get the ADG-styled version of select2 we need to use auiSelect2. The API is roughly the same; the main difference is that we can use the hasAvatar option to get appropriate styling. This means that the picker itself is a standard implementation, as seen in its documentation.

As this is an Atlassian Connect add-on, we can't make AJAX requests directly to JIRA, as they would violate the same-origin policy. To do that we need to use the AP.request mechanism provided by the add-on container. It's interface is very similar to jQuery's ajax method. We can achieve that by specifying a proper transport function, as follows:

$(function onReady() {
    var hostApplicationOrigin = "http://localhost:2990";
    var hostApplicationBaseUrl = "http://localhost:2990/jira";
    var $userDetails = $("#user-details");
    var $userPicker = $("#my-user-picker");
        hasAvatar: true, // auiSelect2 speciffic option, adds styling needed to properly display avatars
        multiple: true, // make the control a multi-select
        ajax: {
            url: "/rest/api/2/user/picker", // JIRA-relative URL to the REST end-point
            type: "GET",
            dataType: 'json',
            cache: true,
            // query parameters for the remote ajax call
            data: function data(term) {
                return {
                    query: term,
                    maxResults: 1000,
                    showAvatar: true
            // parse data from the server into form select2 expects
            results: function results(data) {
                var i, dataLength;
                data = JSON.parse(data);
                return {
                    results: data.users
            // select2 uses $.ajax as  adefault transport function so we have to override it
            // to use AP.request for cross-origin communication
            transport: function transport(params) {
                    url: params.url,
                    headers: {
                        "Accept": "application/json"
                    data: params.data,
                    success: params.success,
                    error: params.error
        // specify id parameter of each user entity
        id: function id(user) {
            return user.key;
        // define how selected element should look like
        formatSelection: function formatSelection(user) {
            var avatarHtml = aui.avatar.avatar({
                size: 'xxsmall',
                avatarImageUrl: hostApplicationOrigin + user.avatarUrl
            return avatarHtml + Select2.util.escapeMarkup(user.displayName);
        // define how single option should look like
        formatResult: function formatResult(user, container, query, escapeMarkup) {
            // format result string
            var resultText = user.displayName + " - (" + user.name + ")";
            var avatarHtml = aui.avatar.avatar({
                size: 'small',
                avatarImageUrl: hostApplicationOrigin + user.avatarUrl
            var higlightedMatch = [];
            // we need this to disable html escaping by select2 as we are doing it on our own
            var noopEscapeMarkup = function noopEscapeMarkup(s) { return s; }
            // highlight matches of the query term using matcher provided by the select2 library
            Select2.util.markMatch(escapeMarkup(resultText), escapeMarkup(query.term), higlightedMatch, noopEscapeMarkup);
            // convert array to string
            higlightedMatch = higlightedMatch.join("");
            return avatarHtml + higlightedMatch;
        // define message showed when there are no matches
        formatNoMatches: function formatNoMatches(query) {
            return "No matches found";

Notice that we're manually escaping the data to be displayed in the formatResult and formatSelection functions. This is to avoid having select2 do the escaping, because in this case we are using HTML to show user's avatars, which shouldn't be escaped.

Step 5. Show QR-code vCard for selected user

For each selected user, we will make an AP.request to fetch the details for that user. If the request is successful, we create a simple vCard, URL-encode it, and generate a QR-code using Google Chart API.

As our add-on is inside an iframe, we also need to make sure we have enough space to avoid displaying scroll bars. Fortunately, by calling 'AP.resize' without arguments, it ensures that the size of the iframe is at least equal to the content's size.

$userPicker.on("change", function onUserPickerChange(e) {
    var data = $userPicker.select2("data");
    if (data.length) {
        $.each(data,function(id, user) {
                url: "/rest/api/2/user",
                headers: {
                    "Accept": "application/json"
                data: {
                    key: user.key
                success: function(data) {
                    data = JSON.parse(data);
                    // simple vCard as text
                    var vcard =
                        "BEGIN:VCARD VERSION:3.0\n" +
                        "FN:" + data.displayName + "\n" + // name
                        "NICKNAME:" + data.name + "\n" + // username
                        "EMAIL;INTERNET;PREF:" + data.emailAddress + "\n" + // e-mail
                        "URL;WORK;PREF:" + hostApplicationBaseUrl + "/secure/ViewProfile.jspa?name=" + data.name + "\n" + // url to user's page in JIRA
                        "PHOTO;PNG:" + data.avatarUrls["48x48"] + "\n" + // url to user's avatar
                        "REV:" + (new Date()).toISOString() + "\n" + // current date
                    $userDetails.append('<img src="http://chart.apis.google.com/chart?cht=qr&chs=400x400&chld=Q&chl=' + encodeURIComponent(vcard) + '" width="400" height="400">');
    } else {

Note: For more about the AP.* helper library, see the Atlassian Connect documentation.

Step 6. Populate the control with data

If we need to, we can also pre-select some users. This is especially handy if the field is persisted by the add-on.

// preselect user with the key "admin"
    url: "/rest/api/2/user",
    headers: {
        "Accept": "application/json"
    data: {
        key: "admin"
    success: function(data) {
        var user = JSON.parse(data);
        // path will be in the first captured group 
        var urlRegexp = /^[^#]*?:\/\/.*?(\/.*)$/;
        $userPicker.select2("data", [{
            key: user.key,
            displayName: user.displayName,
            avatarUrl: urlRegexp.exec(user.avatarUrls['16x16'])[1]

Step 7. See the results

To try it out, follow these steps:

  1. Start the web server from your project home (or from the repository directory, if you downloaded the sample from Bitbucket).
  2. Start the JIRA instance as described in Development Loop.
  3. As an administrative user, install the add-on in JIRA, following the example provided in Hello World tutorial.
  4. Go to your JIRA instance and click the User Browser link in the header to check out the results.