Extension Factories
Extension API
Extension Points

Rate this page:

Extension Points v1 (deprecated)

This is a guide for the deprecated CSE version v1.x.

Visit the updated reference guide for the latest API.

This is a guide for product developers or extension point owners.

First, make sure to install and provide Client-side Extensions in your Product as described in the Installation Guide.

Next, add @atlassian/clientside-extensions-components to your project. These are a set of React components and hooks that will help you use the APIs when your code is written in React. As of this point no other framework is supported.

1
2
3
4
5
yarn add -D @atlassian/clientside-extensions-components

# or

npm install --save-dev @atlassian/clientside-extensions-components

Hooks and components

useExtensions

In order to provide a new extension point, import the useExtensions hook from the @atlassian/clientside-extensions-components package and use it as follows:

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
import React from 'react';
import { useExtensions } from '@atlassian/clientside-extensions-components';

// 1. Define a JSON schema that describes the extension attributes.
// To read more about JSON schema format check the official docs https://json-schema.org/
const extensionPointSchema = {
    type: 'object',
    properties: {
        type: {
            enum: ['button'],
            description: 'This extension point supports a button type',
        },
        label: {
            type: 'string',
            description: 'This button label',
        },
        onAction: {
            typeof: 'function',
            description: 'Callback that will be run when user clicks the button',
        },
    },
};

export default function MyComponent() {
    // 2. Use the `useExtensions` hook to retreive the extensions. The first param passed to the hook is the name of the extension point.
    // This is what the extension developers will use to create extensions so think carefully how to name it.
    // The second is the context value. We will talk about it later.
    // Finally, the third param is the options object where you provide the JSON schema for the extension attributes.
    const extensions = useExtensions('example.extension.point.name', null, { schema: extensionPointSchema });

    return (
        </>
            {extensions.map(extensionDescriptor => {
                // 3. Read the attributes from the descriptor and render the button
                const { key, attributes } = extensionDescriptor;

                return <button key={key} onClick={attributes.onAction}>{attributes.label}</button>
                // ...render them as you need
            })}
        </>
    );
}

What you receive is a list of descriptors that then you can render as you want. The shape of such descriptors is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ExtensionDescriptor {
    // Unique extension key
    key: string;

    // Location, the name of the extension point
    location: string;

    // A number that can be used to decide about the order of extensions
    weight: number;

    // A map of all the attributes provided by the extension author
    attributes: {
        type: string;
        onAction?: () => void;
        [key: string]: unknown;
    };
}

You probably are most interested in the attributes property, which will be populated with the attributes provided by the extensions.

Descriptors are delivered already sorted by weight (from highest to lowest), but you can always sort them differently before rendering.

useExtensionsLoadingState

You can use this hook in order to indicate your users that extensions, and their attributes are loading.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import { useExtensions, useExtensionsLoadingState } from '@atlassian/clientside-extensions-components';
import schema from './schema.json';

export default function MyComponent() {
    const extensions = useExtensions('example.extension.point.name', null, { schema });
    const loading = useExtensionsLoadingState('example.extension.point.name', null, { schema });

    return (
        <>
            {loading ? 'loading...' : extensions.map(ext => (
                // ...render them as you need
            ))}
        </>
    )
}

useExtensionsUnsupported

There might be cases where you want to support legacy WebItems that are created using only the XML definitions inside atlassian-plugin.xml plugin descriptor file.

You can make use of useExtensionsUnsupported hook to gather all those extensions, and then show them as you want.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from 'react';
import { useExtensionsUnsupported } from '@atlassian/clientside-extensions-components';
import schema from './schema.json';

export default function MyComponent() {
    const extensions = useExtensionsUnsupported('example.legacy-web-item-location-name', null, { schema });

    return (
        <>
            {extensions.map((ext) => (
                <a href="ext.url">{ext.label}</a>
            ))}
        </>
    );
}

useExtensionsAll

You can use useExtensionsAll in case you need all the results from useExtensions, useExtensionsLoadingState and useExtensionsLoadingState and prefer to get the results with a single hook.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';
import { useExtensionsAll } from '@atlassian/clientside-extensions-components';
import schema from './schema.json';

export default function MyComponent() {
    const [extensions, legacyExtensions, loading] = useExtensionsAll('example.extension.point.name', null, { schema });

    const allExtensions = [...extensions, ...legacyExtensions].sort((a, b) => a.weight - b.weight);

    return (
        <>
            {loading ? 'loading...' : allExtensions.map(ext => (
                // ...render them as you need
            ))}
        </>
    );
}

Context

You can share some context data with the extensions. Think about the context as the payload of the extension. This is especially useful when you want to provide extension authors with additional data that developers can use to build the extension.

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
import React from 'react';
import { useExtensions } from '@atlassian/clientside-extensions-components';
import extensionSchema from './schema.json'; // A JSON schema that describes the extension attributes

// A JSON schema that describes the extension context
const extensionContextSchema = {
    type: 'object',
    properties: {
        issueKey: {
            type: string,
            description: 'A Jira issue key',
        }
    }
};

export default function IssueView({ issueKey }) {
    const context = {
        issueKey: issueKey,
    };
    const [extensions, isLoading] = useExtensions('example.extension.point.name', context, {
        schema: extensionSchema,
        contextSchema: extensionContextSchema,
    });

    return (
        <>
            {extensions.map(descriptor => (
                // ...render them as you need
            ))}
        </>
    )
}

Schemas and ExtensionPointInfo component

In order to inform extension developers which types of extensions and set of attributes are supported in your locations, you need to define a Schema object.

A Schema is JSON Schema that might looks as this example:

schema.json

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
{
    "type": "object",
    "properties": {
        "type": {
            "type": "string",
            "description": "Supported extension types",
            "enum": ["modal", "link", "button", "panel"]
        },
        "onAction": {
            "description": "Callback triggered on user interaction with the extension. Signature depends on extension type.",
            "type": "function"
        },
        "glyph": {
            "type": "string",
            "description": "Atlaskit Glyph name to render as an icon",
            "enum": ["cross", "check"]
        },
        "tooltip": {
            "type": "string",
            "description": "Tooltip content"
        },
        "hidden": {
            "type": "boolean",
            "description": "Hidden flag to hide the extension"
        },
        "disabled": {
            "type": "boolean",
            "description": "Disabled flag to disable the extension"
        },
        "loading": {
            "type": "boolean",
            "description": "Renders the extension in a loading state (if supported)"
        }
    },

    "required": ["type", "glyph", "tooltip"]
}

You can describe as many attributes as you need, but don't forget to specify the types you support in your extension point in order to avoid unhandled extensions.

Don't set onAction as required if you're accepting Link extensions, since they don't use the onAction API.

ExtensionPointInfo

You can add this component to share with extension developers the schema of your Locations, and also highlight the locations in the current screen.

The information will only be available if the product has enabled their display in development mode.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
import { useExtensions, ExtensionPointInfo } from '@atlassian/clientside-extensions-components';

import schema from './schema.json';
import contextSchema from './context-schema.json';

export default function MyComponent({ context }) {
    // Third param of `useExtensions` is a configuration object.
    // Add the schema as a property of it.
    const [extensions, isLoading] = useExtensions('example.extension.point.name', context, { schema, contextSchema });

    return (
        <>
            <h4>
                Product Location
                <ExtensionPointInfo name="example.extension.point.name" schemas={{ schema, contextSchema }} />
            </h4>
            {extensions.map(ext => (
                // ...render them as you need
            ))}
        </>
    )
}

Render the ExtensionPointInfo in a place that's visible even in edge cases, like when the location is inside a dropdown (place it in the dropdown trigger), or part of a Grid (place it in the header).

Default extension types and handlers

Handlers continue to work the same on the recent version of CSE. Refer to the extension handlers guide to learn how to use them.

Refer to the extension handlers guide.

Rate this page: