Last updatedJul 19, 2021

Rate this page:

Creating an extension point

In this first section, you will learn:

  • What an extension point is
  • What schemas are and how to define them
  • How to list all the registered extensions for your extension point

You'll be working on the tutorial page: http://localhost:7990/bitbucket/plugins/servlet/extension-points

Extension points

Extension points allow you to provide a safe place for other developers to extend your UI with their own features.

An extension point uses strongly typed contracts called schemas to specify the extensions they support and the attributes they can render. The validations safeguard the extension point against runtime errors.

This contract is called a schema, and is the central piece of CSE extension points.

Defining a schema

You define schemas in .graphql files using graphql-style syntax, which lets you create complex, strongly typed structures to define lightweight validators that can run without affecting performance.

In /src/main/my-app/extensions/extension-points-tutorial, create a file called schema.cse.graphql and define a schema:

1
2
3
4
5
6
7
8
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {

}

The basic structure of a CSE schema is:

  • A graphql type named Schema.
  • A comment annotation on top of the Schema type to specify the name of the extension point to be created.

Supported attributes

In Schema, you can declare the attributes that determine how the extension is rendered.

For example, if you want to support a Link extension, you would write a schema like the following:

1
2
3
4
5
6
7
8
9
10
11
12
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: LinkExtension!

    label: String

    url: String
}

The CSE schema package provides a list of default Scalars and Types like LinkExtension and String to make it easier to define schemas.

In the above example, the type attribute declares that only extensions of type Link are supported. The attributes label and url are strings to be used when rendering the link on the screen.

When writing schemas, you should verify that the attributes you're requiring comply with CSE extension factories API.

You can require as many attributes as you need, of any type you want, but there are certain attributes like type, onAction, label and url that are expected to behave in a certain way. Make sure to check the extension factories API reference to verify that your schema is requiring the attributes that developers expect for a certain type of extension.

Attribute unions

In some scenarios, you might need an attribute to have more than one type. For example, you might want to support both a link extension and button extension on the same extension point.

You can declare these attributes using a graphql union syntax as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: SupportedExtensions!

    label: String

    url: String

    onAction: Function
}

union SupportedExtensions = LinkExtension | ButtonExtension

In the above example, you can see that an onAction attribute was also declared to comply with the Button extension API.

Getting the extensions

Once the schema for an extension point is defined, it can be imported directly into the code using the CSE schema-loader.

The CSE schema-loader is already installed and configured in the Bitbucket Server template for you.

The loader will then transform your schema into a set of hooks and components that fetches all the extensions registered for your extension point, validate them against the required attributes, and return a list of extension descriptors.

Extension descriptor

An extension descriptor is an object that contains the key and location of an extension, in addition to its attributes:

1
2
3
4
5
6
7
interface ExtensionAttribute {
    key: string;
    location: string;
    attributes: {
        // the attributes of an extension
    };
}

Importing the helpers

To get the helpers that fetch and validate the extensions, import them from the schema as follows.

On ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx, write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react';

import { useExtensions } from './schema.cse.graphql';

const MyPage = () => {
    // get all supported extensions registered for extension.points.tutorial
    const extensions = useExtensions();

    console.log(extensions);

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
        </PageContainer>
    );
};

/** page declaration for guides only **/

Go to http://localhost:7990/bitbucket/plugins/servlet/extension-points and open your console.

You should see a list of extension descriptors like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
    {
        key: "com.atlassian.myapp.bitbucket-plugin-template:extensions__extension-points-tutorial__extensions__button-extension__js",
        location: "extension.points.tutorial",
        attributes: {
            label: "Tutorial: button extension",
            onAction: ƒ (),
            type: "button"
        }
    },
    {
        key: "com.atlassian.myapp.bitbucket-plugin-template:extensions__extension-points-tutorial__extensions__link-extension__js"
        location: "extension.points.tutorial",
        attributes: {
            label: "Tutorial: link extension"
            type: "link"
            url: "http://go.atlassian.com/clientside-extensions"
        }
    }

These extensions are provided by the template in order to make it easier to follow these guides, and are located under /src/main/my-app/extensions/extension-points-tutorial/extensions folder.

Showing a loading indicator

The CSE schema-loader provides other helpers and components. For example, it provides a hook to tell if extensions are being loaded.

On ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx, write:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';

import { useExtensions, useExtensionsLoadingState } from './schema.cse.graphql';

const MyPage = () => {
    const extensions = useExtensions();
    const loading = useExtensionsLoadingState();

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {loading && (
                <p>
                    <b>loading...</b>
                </p>
            )}
        </PageContainer>
    );
};

/** page declaration for guides only **/

For a list of all the helpers and components provided by the schema-loader, refer to Hooks and components guide.

Recap and next steps

So far, you've learned:

  • Extension points allow other developers to enhance your UI with more features.
  • Each extension point is defined as a schema, which is a contract between your extension point and developers.
  • How to get the extensions registered for your extension point, and how to show a loading indicator while they load.

Next, you're going to learn how to render your extensions.

Rate this page: