---
title: Rendering extensions - Client-side Extensions
platform: server
product: clientside-extensions
category: devguide
subcategory: extension-points
date: '2024-09-24'
---

# Rendering extensions

{{% note %}}
This is a continuation of the work done in [1. Creating an extension point](/server/framework/clientside-extensions/guides/extension-points/creating-an-extension-point).
{{% /note %}}

In this section you will learn how to:

-   Render extension using their attributes
-   Use handlers
-   Render complex extensions like modals and panels

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

## Using attributes

On the previous guide, you defined a schema for an extension point and imported the helpers
using the CSE schema-loader to fetch and validate the extensions.

You can call the `useExtensions` hook helper to retrieve a list of extension descriptors. An
extension descriptor is nothing more than an object that contains the attributes of an
extension like:

```ts
interface ExtensionAttribute {
    key: string;
    location: string;
    attributes: {
        // the attributes of an extension
    };
}
```

After you get the extension, you can make use of their attributes as you see fit. For
example, if you want to render link and button extensions, you can do so as follows:

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

```jsx
import React from 'react';

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

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

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'link':
                return (
                    <a href={attributes.url} key={key}>
                        {attributes.label}
                    </a>
                );
            case 'button':
                return (
                    <button type="button" onClick={attributes.onAction} key={key}>
                        {attributes.label}
                    </button>
                );

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/
```

Go to http://localhost:7990/bitbucket/plugins/servlet/extension-points, and you should see
a link and a button extension rendered on the screen.

## Using handlers

Rendering the same type of extensions can become repetitive, and even time consuming for
more complex examples. That's why CSE provides a set of components called handlers, that
will take the attributes and render them, also matching the style of Atlassian products
automatically for you.

You could rewrite the previous code to use Link and Button handlers as follows:

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

```jsx
import React from 'react';
import { LinkHandler, ButtonHandler } from '@atlassian/clientside-extensions-components';

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

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

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'link':
                return (
                    <LinkHandler href={attributes.url} key={key}>
                        {attributes.label}
                    </LinkHandler>
                );
            case 'button':
                return (
                    <ButtonHandler onAction={attributes.onAction} key={key}>
                        {attributes.label}
                    </ButtonHandler>
                );

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/
```

The handler code for links and buttons is similar, except that the button
is rendered with the same styles as Atlassian products without extra effort.

{{% tip %}}
To learn more about handlers, refer to the [handlers reference](/server/framework/clientside-extensions/reference/api/extension-points/extension-handlers/) guide.
{{% /tip %}}

## Modals

[Modal extensions](/server/framework/clientside-extensions/reference/api/extension-api/modal-api/)
allow developers to render a button that displays a modal with custom content when clicked.

The API for Modal extensions is quite complex to set up due to all its different options. In this
scenario, you’ll need to use handlers.

First, modify your schema to accept extensions of type `ModalExtension` on
`/src/main/my-app/extensions/extension-points-tutorial/schema.cse.graphql`:

```graphql
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: SupportedExtensions!

    label: String

    url: String

    onAction: Function
}

union SupportedExtensions = LinkExtension | ButtonExtension | ModalExtension
```

Then, modify your extension point to render modals with the modal handler on
`./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx`:

```jsx
import React from 'react';
import { ModalWithActionHandler } from '@atlassian/clientside-extensions-components';

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

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

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'modal':
                return (
                    <ModalWithActionHandler render={attributes.onAction} key={key}>
                        {attributes.label}
                    </ModalWithActionHandler>
                );

            /** other cases **/

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/
```

The provided modal handler will create a [Modal API](/server/framework/clientside-extensions/reference/api/extension-api/modal-api/)
object and pass it to the extensions, and will also create an implementation of
this API using an Atlaskit modal dialog.

If you refresh the page, you should see a button that opens a modal when clicked.

## Panels

[Panel extensions](/server/framework/clientside-extensions/reference/api/extension-api/panel-api/) allow developers to render any HTML content inside a given container.

Even though the API for panels is simpler than modals, it can still be challenging to handle
the rendering and clean-up cycles efficiently every time the extension point re-renders.

The panel handle takes care of this problem by creating an empty container and handling
the rendering and cleanup cycles for you.

First, modify your schema to accept extensions of type `PanelExtension` on
`/src/main/my-app/extensions/extension-points-tutorial/schema.cse.graphql`:

```graphql
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: SupportedExtensions!

    label: String

    url: String

    onAction: Function
}

union SupportedExtensions = LinkExtension | ButtonExtension | ModalExtension | PanelExtension
```

Then, modify your extension point to render panels with the panel handler on
`./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx`:

```jsx
import React from 'react';
import { PanelHandler } from '@atlassian/clientside-extensions-components';

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

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

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            /** other cases **/

            case 'panel':
                return <PanelHandler render={attributes.onAction} key={key} />;

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/
```

You should now see an extension rendered with a title "Look!" and a paragraph that counts the
times the panel has been re-rendered.

## Recap and next steps

So far, you've learned:

-   How to use attributes to render extensions
-   How to use handlers to render extensions

Next, you're going to learn how to [provide context to extensions](/server/framework/clientside-extensions/guides/extension-points/providing-context).
