JavaScript API

Bitbucket Server exposes a JavaScript API for use in building your plugin's UI and behavior in the browser.

JSDoc

Compatibility Policy

The JS API's compatibility policy matches that of Bitbucket's other APIs. JS APIs will not contain breaking changes between minor versions (e.g. 4.0 and 4.1). Instead, any removed or modified methods and properties will log warnings in the browser console when used, and the legacy behavior will cease to work in the next major release (e.g. Bitbucket Server 5.0).

"Breaking changes" describes incompatible changes in behavior when valid inputs are given to any API functions, and incompatible type changes in data properties. Behavior for invalid inputs may change at any release, and mutation of API modules is not supported. See API Scope for details.

Loading the APIs

JavaScript APIs are not guaranteed to be available on the page. Before using an API you MUST depend on the matching web-resource module for that API. Each JS module will list its web resource key, which should be listed as a <dependency>{key}</dependency> in your <web-resource/> module. See the Web Resource Plugin Module documentation for details.

Asynchronous Module Definition (AMD)

require()

Our JavaScript APIs are delivered using Asynchronous Module Definitions (AMD) patterns. This means they can referenced with the global function require. Given an array of string identifiers, require will call your callback with the modules referenced.

1
2
3
require(['bitbucket/feature/files/file-handlers', 'some/other/module'], function(fileHandlers, someOtherModule) {
    // do things with the file-handlers module and some other module.
});

Asynchronous execution

It is important to note that your callback to the require function MAY be executed asynchronously. You should not depend on your callback being called by any certain point in time. For example, you should not expect that your code is executed before DOMContentLoaded (also known as $(document).ready())

Synchronous modules

For some but not all modules we also support a synchronous version of require, usable like var module = require("bitbucket/x"). This is to support use cases like registering a File Handler. These modules will explicitly note their synchronous require support in documentation, and synchronous support should not be otherwise assumed, even if it happens to work when attempted.

See the "Events and AMD" section below for an example of combining synchronous and asynchronous dependencies.

define()

You can also define your own modules using define. Note that you SHOULD prefix any modules you define with a unique key (e.g. "mycompany/") to avoid conflicts with Bitbucket or other plugins. You MUST NOT begin your module names with "bitbucket/", "atlassian/", or "internal/". These prefixes are explicitly reserved for use by Atlassian Bitbucket core.

The format of a define call is define(module name string, array of dependency ids, callback that returns the module)

1
2
3
4
define('mycompany/things/helper', ['any', 'module', 'dependencies'], function(any, module, dependencies) {
    var helper = { ... };
    return helper;
});

It should be noted that, like require, define MAY be called asynchronously, and you should NOT depend on your callback being called by any particular point in time.

Good module names

  • mycompany/my-module
  • mycompany/my-other-module
  • mycompany/my-topic/my-third-module

Bad module names

  • my-module
  • bitbucket/my-module
  • atlassian/my-module
  • internal/my-module

API Scope

The scope of the JS API is limited to the require and define global functions, and within that, the "aui" module and any modules prefixed with "bitbucket/" except those prefixed with "bitbucket/internal". Use of any modules with the "bitbucket/internal" prefix is not covered by this API and SHOULD NOT be used. Similarly, use of any global properties or functions is not covered by this API and SHOULD NOT be used.

Private naming (_ or internal. prefix)

Any property whose name begins with an underscore is considered private and SHOULD NOT be used. E.g. publicObject._privateProperty may not be stable between versions of Bitbucket, but publicObject.publicProperty will be.

Similarly, events named starting with internal. are for internal use and will not be stable between releases of Bitbucket.

Data properties

Public data properties will guarantee the following between minor versions:

  • If a value is a primitive or object Number, Boolean, String, Array, Date, Function or other Object, it will continue to be typeof or instanceof that type.
  • If a value is instanceof Object (including Arrays, Dates, and Functions), these stability guarantees apply recursively to all non-private properties on that object.

Functions

When public functions are called, we guarantee the following between minor versions:

  • Any arguments that are valid input to the function as specified in the JS API Reference will remain valid between minor versions.
  • When called with valid arguments, the return value will remain valid and will be of the return type(s) specified in the reference docs.

Browser Support

The API's behavior is guaranteed stable only within the browsers supported by Bitbucket for any given release. Since our browser support is not guaranteed stable between minor versions, this means the supported browsers for the API may change between minor versions. For example, if support for IE11 is removed in Bitbucket Server 4.x, the behavior of API methods is no longer guaranteed to work in IE11 in Bitbucket Server 4.x+.

Explicitly out of scope

The following uses are explicitly out of scope for the API. Their behavior is undefined and we make no guarantees about their stability.

  • Calling a method property of an object (e.g. foo.doSomething()) with any this value that isn't that exact object (e.g. foo.doSomething.call(bar)).
  • Calling any function with a non-Object this value (e.g., false, null, "string"). We 'use strict' which means these may behave differently in different browsers.

And note that the following MAY change between minor releases:

  • Behavior when calling a function with more arguments than are specified in the reference documentation. We may add extra arguments to functions in minor releases.

    • UNSUPPORTED: threeArgFunc(one, two, three, four)
  • Behavior when calling a function with object arguments that contain extra properties. These properties may gain meaning in a minor release (e.g. new options are supported).

    • UNSUPPORTED: func({ validOption: 'a', somethingExtra : 'invalid' })
  • When omitting an optional argument or argument property in a function call, the default behavior of that function may change (but will remain valid).

    • Bitbucket x.y: getPrimaryColor() === 'blue'
    • Bitbucket x.y+1: getPrimaryColor() === 'yellow'
  • Properties on an object's prototype may become enumerable properties on the object itself, or visa versa.

  • The configuration of a property may change: (e.g., a value may be replaced by get/set pairs, may change enumerable or writable values, etc).

Useful JS patterns

There are a few useful patterns when working against the Bitbucket JS API. Some are also useful in JS at large.

Immediately-invoked function expressions (IIFE)

When using an AMD module, your code is within a closure, and thus is protected and (somewhat) sandboxed from the global environment. E.g.,

1
2
3
require(['some', 'modules'], function() {
    // code in here can be isolated
});

When not using AMD, you should isolate your code using an IIFE, so it is less likely to affect or be affected by the global scope:

1
2
3
(function() {
    // this code has the same isolation benefits.
})();

This ensures that you won't inadvertently create any global variables that have naming conflicts with another plugin.

Promises

We use a lot of Promises in Bitbucket. Specifically we use the jQuery implementation of Promises and Deferreds. Promises allow you to pass around a result before that result is actually calculated and available. They are similar to Futures. For any function, rather than passing in callbacks that will be called when a result is ready, we generally pass out a Promise you can observe for success or failure.

E.g. where we might have had a function like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function cookBacon(cookTime, tastyBaconCallback, ruinedBaconCallback) {
    var burner = getStove().getBurner();
    var pan = getPan();
    var bacon = getBacon();

    pan.add(bacon);
    burner.put(pan);
    burner.light('medium');

    setTimeout(function() {
        if (!bacon.isCrispy()) {
            ruinedBaconCallback('Bit longer next time.');
        } else if (bacon.isBurnt()) {
            ruinedBaconCallback('Bit less next time.');
        } else {
            tastyBaconCallback(bacon);
        }
    }, cookTime);
}

We do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function cookBacon(cookTime) {
    var deferred = new $.Deferred();

    var burner = getStove().getBurner();
    var pan = getPan();
    var bacon = getBacon();

    pan.add(bacon);
    burner.put(pan);
    burner.light('medium');

    setTimeout(function() {
        if (!bacon.isCrispy()) {
            deferred.reject('Bit longer next time.');
        } else if (bacon.isBurnt()) {
            deferred.reject('Bit less next time.');
        } else {
            deferred.resolve(bacon);
        }
    }, cookTime);

    return deferred.promise();
}

Note that the interface becomes simpler - there are no more callbacks required. If you want to wait for the bacon, you write code like:

1
2
3
4
5
cookBacon(5 * 60 * 1000).then(function success(bacon) {
    eat(bacon);
}, function failure(suggestion) {
    console.log(suggestion);
})

The biggest advantages to Promises are seen when you begin to chain them together, or wait on multiple of them. Check out jQuery's Promise documentation for more information.

Events and AMD

Because AMD callbacks may not be called synchronously, it's recommended that you listen for events outside of a require call when you expect those events to occur before or soon after the page is loaded. The events module is one of a few that can be loaded synchronously.

E.g.

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
(function() {
    var registry;
    var things;

    function tryInit() {
        if (registry && things) {
            things.foo();
            registry.enableContext('my-shortcut-context');
        }
    }

    // Synchronously get a reference to the `events` module.
    var events = require('bitbucket/util/events');
    // Listen for our event
    events.on('stash.widget.keyboard-shortcuts.register-contexts', function(r) {
        registry = r;
        tryInit();
    });

    // Require any dependencies we need separately from attaching our event listeners.
    require(['things'], function(t) {
        things = t;
        tryInit();
    });
})();

This example could be simplified with the use of Promises as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function() {
    var gotThings = new $.Deferred();

    // Require any dependencies we need separately from attaching our event listeners.
    require(['things'], gotThings.resolve.bind(gotThings));

    // Synchronously get a reference to the `events` module.
    var events = require('bitbucket/util/events');
    // Listen for our event
    events.on('stash.widget.keyboard-shortcuts.register-contexts', function(registry) {
        gotThings.then(function(things) {
            things.foo();
            registry.enableContext('my-shortcut-context');
        });
    });
})();