---
title: Extension Handlers - Client-side Extensions
platform: server
product: clientside-extensions
category: reference
subcategory: api
date: '2024-09-24'
---

# Extension Handlers

Client-side Extensions comes with a set of default extension types, each requiring their own API to work correctly.

Some of them are very simple, but others require a large API that can be difficult to provide on your own, so
CSE provides default handlers that help you render extensions with Atlaskit components.

These default handlers are provided by the `@atlassian/clientside-extensions-components` package, so make sure to
install it:

```
yarn add -D @atlassian/clientside-extensions-components

// or

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

Here are some examples on how to render each type of extension, and how to use these default handlers.

## Link

Links are a static set of attributes with at least a `url` and `label` property. You can then decide how to handle
the case of rendering links as you see fit.

#### `schema.cse.graphql`

```graphql
"""
---
extensionPoint: example.extension-point
---
"""
type Schema {
    type: LinkExtension!

    label: String!

    url: String!
}
```

#### `my-extension-point.jsx`

```jsx
import React from "react";
import { useExtensions } from "./schema.cse.graphql";

const ExampleExtensionPoint = ({ context }) => {
    const extensions = useExtensions(context);

    return (
        <>
            {extensions.map(({attributes}) => (
                <a href={attributes.link}>{attributes.label}</a>
            )}
        </>
    )
}

export default ExampleExtensionPoint;
```

## Button

Buttons provide a way for extension developers to execute an action when their button is clicked.

They declare such action by adding an `onAction` method to their attributes, so you should bind that method to your
button click handler when rendering.

#### `schema.cse.graphql`

```graphql
"""
---
extensionPoint: example.extension-point
---
"""
type Schema {
    type: ButtonExtension!

    label: String!

    onAction: Function!
}
```

#### `my-extension-point.jsx`

```jsx
import React from "react";
import { useExtensions } from "./schema.cse.graphql";

const ExampleExtensionPoint = ({ context }) => {
    const extensions = useExtensions(context);

    return (
        <>
            {extensions.map(({attributes}) => (
                <button onClick={attributes.onAction}>{attributes.label}</button>
            )}
        </>
    )
}

export default ExampleExtensionPoint;
```

## Panel

Panels provide a container for extension developers to render custom HTML content (they are the equivalent of legacy web-panels).

In this case, the `onAction` method should receive an API with a `onMount` and `onUnmount` methods,
and you should handle calling this methods with the arguments when needed.

Since this can be a bit difficult to implement, CSE provides a default handler that creates a container and a Panel API object for you.

### Supported props

<table>
    <thead>
        <tr class="header">
            <th>Prop</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>render</code> <strong>*</strong></td>
            <td><code>function</code></td>
            <td>The `onAction` attribute of an extension that recieves a <a href="/server/framework/clientside-extensions/reference/api/extension-api/panel-api/">Panel API object.</a></td>
        </tr>
        <tr>
            <td><code>context</code></td>
            <td><code>any</code></td>
            <td>An object representing some context to be shared at the moment of rendering. This is mostly used for AsyncPanels</td>
        </tr>
        <tr>
            <td><code>RootType</code></td>
            <td><code>string</code></td>
            <td>The type of HTML element to render the panel into. Default: <b>div</b>.</td>
        </tr>
    </tbody>
</table>

<p><strong>* required</strong></p>

### Usage

#### `schema.cse.graphql`

```graphql
"""
---
extensionPoint: example.extension-point
---
"""
type Schema {
    type: PanelExtension!

    onAction: PanelOnAction!
}
```

#### `my-extension-point.jsx`

```jsx
import React from "react";
import { PanelHandler } from "@atlassian/clientside-extensions-components";
import { useExtensions } from "./schema.cse.graphql";

const ExampleExtensionPoint = ({ context }) => {
    const extensions = useExtensions(context);

    return (
        <>
            {extensions.map(({attributes}) => (
                <PanelHandler render={attributes.onAction} />
            )}
        </>
    )
}

export default ExampleExtensionPoint;
```

## AsyncPanel

Async panels work very similar to regular Panels, with the difference that they defer the loading of the
resources needed to render until they are necessary. They are useful when loading very heavy features that
are not always rendered.

CSE also provides a default handler for this kind of extensions.

### Supported props

<table>
    <thead>
        <tr class="header">
            <th>Prop</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>location</code> <strong>*</strong></td>
            <td><code>string</code></td>
            <td>The name of the extension point used to provide better debugging information to developers when an error happens with their extension.</td>
        </tr>
        <tr>
            <td><code>fallback</code> <strong>*</strong></td>
            <td><code>JSX.Element</code></td>
            <td>A JSX element to render while the extension render module is loading.</td>
        </tr>
        <tr>
            <td><code>renderProvider</code> <strong>*</strong></td>
            <td><code>function</code></td>
            <td>The `onAction` attribute of an <a href="/server/framework/clientside-extensions/reference/api/extension-factories/async-panel/">AsyncPanel</a> extension.</td>
        </tr>
        <tr>
            <td><code>contextProvider</code></td>
            <td><code>fuction</code></td>
            <td>A function used to retreive the context to be shared with the extension before rendering.</td>
        </tr>
        <tr>
            <td><code>extensionKey</code></td>
            <td><code>string</code></td>
            <td>They key of the extension comming from its descriptor. Useful for better debugging information to developers when an error happens with their extension.</td>
        </tr>
        <tr>
            <td><code>RootType</code></td>
            <td><code>string</code></td>
            <td>The type of HTML element to render the panel into. Default: <b>div</b>.</td>
        </tr>
    </tbody>
</table>

<p><strong>* required</strong></p>

### Usage

#### `schema.cse.graphql`

```graphql
"""
---
extensionPoint: example.extension-point
---
"""
type Schema {
    type: AsyncPanelExtension!

    onAction: AsyncPanelOnAction!
}
```

#### `my-exension-point.jsx`

```jsx
import React from "react";
import { AsyncPanelHandler } from "@atlassian/clientside-extensions-components";
import { useExtensions } from "./schema.cse.graphql";

const ExampleExtensionPoint = ({ context }) => {
    const extensions = useExtensions(context);

    return (
        <>
            {extensions.map(({key, attributes}) => (
                <AsyncPanelHandler
                    location="example.extension-point"
                    extensionKey={key}
                    renderProvider={attributes.onAction}
                    contextProvider={attributes.contextProvider}
                    fallback={<div>loading...</div>}/>
            )}
        </>
    )
}

export default ExampleExtensionPoint;
```

## Modal

Modals provide a button that opens a modal, and then a container in the body of that modal to render custom HTML content.
It also provides a set of APIs to interact with the modal.

A handler is provided to ease the implementation of this large API.

### Supported props

<table>
    <thead>
        <tr class="header">
            <th>Prop</th>
            <th>Type</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><code>render</code> <strong>*</strong></td>
            <td><code>function</code></td>
            <td>The `onAction` attribute of an extension that recieves a <a href="/server/framework/clientside-extensions/reference/api/extension-api/modal-api/">Modal API object.</a></td>
        </tr>
        <tr>
            <td><code>isOpen</code></td>
            <td><code>boolean</code></td>
            <td>A boolean that indicates if the handler should open the modal.</td>
        </tr>
        <tr>
            <td><code>onClose</code></td>
            <td><code>function</code></td>
            <td>A callback to be executed when the modal is closed.</td>
        </tr>
    </tbody>
</table>

<p><strong>* required</strong></p>

### Usage

#### `schema.cse.graphql`

```graphql
"""
---
extensionPoint: example.extension-point
---
"""
type Schema {
    type: ModalExtension!

    label: string!

    onAction: ModalOnAction!
}
```

#### `my-extension-point.jsx`

```jsx
import React from "react";
import { ModalWithActionHandler } from "@atlassian/clientside-extensions-components";
import { useExtensions } from "./schema.cse.graphql";

const ModalExtensionRenderer = ({ extension }) => {
    const { onAction, label, ...attributes } = extension.attributes;

    return (
        <ModalWithActionHandler render={onAction} {...attributes}>
            {label}
        </ModalWithActionHandler>
    );
};

const ExampleExtensionPoint = ({ context }) => {
    const extensions = useExtensions(context);

    return (
        <>
            {extensions.map((extension) => (
                <ModalExtensionRenderer extension={extension} />
            )}
        </>
    )
}

export default ExampleExtensionPoint;
```
