---
title: Custom HTML content - Client-side Extensions
platform: server
product: clientside-extensions
category: devguide
subcategory: introduction
date: '2024-09-24'
---

# Custom HTML content

{{% note %}}
This is a continuation of the work started in [3. Using extension API](/server/framework/clientside-extensions/guides/introduction/using-extension-api/).
{{% /note %}}

In this section you will learn:

-   How you can add custom HTML content using extensions.

## Panel extension

So far, your extension has been rendered entirely by the product, and you've only been able to modify some of its aspects via attributes.
But, sometimes your extension might be more than a button or a link, and you would like to render your own content.

For those cases, Client-side Extensions (CSE) provides an extension type called **Panel**.

For panel extensions, CSE executes the `onAction` method when it's ready to receive your content, and provides a [Panel API](/server/framework/clientside-extensions/reference/api/extension-api/panel-api/)
object that you can use to render your content.

{{% note %}}
Remember that the `onAction` method behaves differently for each type of extension. For more info, see [Extension API reference](/server/framework/clientside-extensions/reference/api/extension-factories).
{{% /note %}}

### Creating a pull request comment

Bitbucket provides an extension points that supports panels only inside a pull request, in the comments section of a `diff`. To continue, you'll need to add a comment to the `diff` of your pull request:

1. In your pull request, open the **Diff** tab.
2. In the toolbar, click **Add comment to this file**.
3. Write a comment and save it.

## onMount

Use `onMount` to get a container created by CSE and use it to render your content in it.

### Add a panel extension and render your content

After creating a comment in the `diff` section, change your extension to be a panel, and then use the `onMount` method as follows.

In the `/src/my-app/extensions/first-extension.js` file:

1. Use the `bitbucket.ui.pullrequest.comment.extra` extension point.
2. Import `PanelExtension` from `@atlassian/clientside-exstensions`.
3. Change your extension to use the `PanelExtension` factory.
4. Use `onAction` to receive the `PanelApi` object.
5. Use `onMount` to get a container and render your content in it.

```js
// #/src/my-app/extensions/first-extension.js
import { PanelExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.comment.extra
 */
export default PanelExtension.factory((extensionAPI, context) => {
    return {
        onAction: (panelAPI) => {
            panelAPI.onMount((container) => {
                container.innerHTML = `
                    <h2>Look!</h2>
                    <p>this is the panel content</p>
                `;
            });
        },
    };
});
```

After refreshing the `diff` page, you should see the content rendered in the bottom part of the comment.

## onUnmount

You can use `onUnmount` to specify the cleanup code you'd like to run when your panel is about to be destroyed or re-rendered.

### Unmount after your content is destroyed

Using the same example with intervals, create an extension that updates its content every 3 seconds and clears the
interval after you navigate away from the page.

In the `/src/my-app/extensions/first-extension.js` file:

1. In `onMount`, create an interval that updates your content every 3 seconds.
2. Use `onUnmount` to clear the interval and the container where you rendered your content.

```js
// #/src/my-app/extensions/first-extension.js
import { PanelExtension } from '@atlassian/clientside-extensions';

/**
 * @clientside-extension
 * @extension-point bitbucket.ui.pullrequest.comment.extra
 */
export default PanelExtension.factory((extensionAPI, context) => {
    return {
        onAction: (panelAPI) => {
            let interval;
            let count = 1;

            const getContent = () => `
                <h2>Look!</h2>
                <p>this panel has been rendered ${count} times</p>
            `;

            panelAPI.onMount((container) => {
                interval = setInterval(() => {
                    console.log('interval executed');
                    count++;
                    container.innerHTML = getContent();
                }, 3000);

                container.innerHTML = getContent();
            });

            panelAPI.onUnmount((container) => {
                console.log('interval cleared');
                clearInterval(interval);
                container.innerHTML = '';
            });
        },
    };
});
```

Refresh the `diff` page and check that the content of your panel is updated every 3 seconds. Now, navigate to
the commits page and check that the interval is cleared and no longer running.

## Custom content with React

Client-side Extensions (CSE) provides a utility that makes it easier to use React with the **Modal** extension. For more info, see [Rendering elements as React](/server/framework/clientside-extensions/reference/api/extension-api/render-element-as-react/).

## Recap and next steps

You've learned how to use the APIs available to create extensions that render your content.

Next, you'll learn how to [create modals](/server/framework/clientside-extensions/guides/introduction/creating-a-modal).
