UI Kit components
Jira UI Kit components
UI Kit hooks
Forge bridge APIs
Jira bridge APIs
Upgrade UI Kit versions
Previous versions

Forge’s EAP offers experimental features to selected users for testing and feedback purposes. These features are unsupported and not recommended for use in production environments. They are also subject to change without notice.

For more details , see Forge EAP, Preview, and GA.

Frame (EAP)

Frame component acts as a container for rendering static frontend applications, such as HTML, CSS, and JavaScript, ensuring seamless integration with the UI Kit.

The Frame component offers two methods for integrating custom UI resources within the UI Kit:

  1. Frame
  2. createFrame.

The first method, Frame, is a straightforward approach that involves directly importing the Frame component into your app. It provides flexibility in implementing desired user interfaces and supports communication with the main app through the Events API, allowing bidirectional and broadcast communication.

Alternatively, the createFrame method introduces a react-like helper function that simplifies communication between the UI Kit and the Frame component. By allowing the passing of custom properties, it reduces the need for event-based communication, offering a streamlined approach for developers who prefer a React-like syntax. This method is particularly useful for applications where basic communication through properties suffices, enhancing ease of use and reducing complexity.

Setting up resources

Setting up a resource for Frame and createFrame is similar to setting up for a Custom UI app, as documented on the Resources page.

Steps to set up a resource:

  1. Create a directory: In the root of the app directory, create a directory for the resource. For example, resources/my-frame-component.

  2. Add static frontend files: Include the static frontend files such as, HTML, CSS, JavaScript, images, and videos in the resource directory. The entry point should be an index.html file. For React or Vue-based frontends, refer to the accessing static assets section in the Custom UI documentation.

  3. Update manifest.yml: Add the resource key and path to the manifest.yml file.

Example directory structure

1
2
hello-world-app
├── manifest.yml
├── resources
│   └── my-frame-component
│       └── index.html
└── src
    ├── frontend
    │   └── index.jsx
    └── index.js

Example manifest.yml file

1
2
modules:
  confluence:globalPage:
    - key: frame-example
      resource: main
      render: native
      title: Hello world!
resources:
  - key: example-frame-resource  # This is the resource key for the Frame component
    path: resources/my-frame-component
  - key: main  # This is the resource key for the UI Kit part of the app
    path: src/frontend/index.jsx

Using Frame

To add the Frame component to your app:

1
2
import { Frame } from '@forge/react';

Props

The Fame component has the following properties that need to be considered:

NameTypeRequiredUsageDescription
resourcestringYesFrame componentThis is the key of the resource to be loaded in the Frame. The resource must be defined in the app’s manifest.yml file. Refer to the guidelines below for defining a resource for Frame.

Example in UI Kit index file

1
2
import React from 'react';
import ForgeReconciler, { Text, Frame } from '@forge/react';

const App = () => {
  return (
    <>
      <Text>[UI Kit] Hello world!</Text>
      <Frame resource="example-frame-resource" />
    </>
  );
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Communication between UI Kit and Frame

Using events API

The Events API communication is both bidirectional and broadcast:

  • Bidirectional: Events can be sent from both the UI Kit and Frame component using events.emit.

  • Broadcast: A single event can be received by multiple targets (e.g., different instances or extensions of the same app) through the events.on mechanism, as the Events API design enables communication among Custom UI extensions.


The primary mechanism for communication between the Forge UI Kit (main app) and the `Frame` component is the [Events API](/platform/forge/apis-reference/ui-api-bridge/events/) within `@forge/bridge`.
  • events.emit: This function sends data from either the UI Kit or Frame component to the counterpart.

  • events.on: This function listens to and handles data transmitted via events.emit.

Example of using events.emit

The following example demonstrates how you can use the event.emit function to send data from the UI Kit to the Frame component:

1
2
// This is the UI Kit part of the app, e.g., `src/frontend/index.jsx` for a Forge app

import React, { useEffect } from 'react';
import ForgeReconciler, { Frame } from '@forge/react';
import { events } from '@forge/bridge';

const App = () => {
  useEffect(() => {
    // Send a message to the Frame component
    setTimeout(() => {
      events.emit('MY_FRAME_RESOURCE_DATA', { msg: 'hello' });
    }, 2000);
  }, []);
  return <Frame resource="example-frame-resource" />;
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Example of using events.on

The following example shows how you can use the event.on function to receive data from the UI Kit in the Frame component:

1
2
// This is the Frame resource part of the app, e.g., `resources/frame-app/src/App.jsx` for a Forge app

import React, {useEffect, useState } from 'react';
import { events } from "@forge/bridge";

function App() {
  const [msg, setMsg] = useState('');

  useEffect(() => {
    let subscription = null;
    const registerMessage = async () => {
      subscription = await events.on('MY_FRAME_RESOURCE_DATA', ({ msg }) => {
        // This will be called whenever the message is sent from the UI Kit
        setMsg(msg);
      });
    };
    registerMessage();
    return () => {
      if (subscription) {
        subscription.then(({ unsubscribe }) => unsubscribe());
      }
    };
  }, [setMsg]);

  return <b>{msg}</b>;
}

Using createFrame

To add the createFrame helper function to your app:

1
2
import { createFrame } from '@forge/react';

Function signature

1
2
function createFrame<CustomFrameProps extends Record<string, unknown>>(
  resource: string,
  arePropsEqual?: TArePropsEqual<CustomFrameProps>
): React.FC<CustomFrameProps>;

type TArePropsEqual<CustomFrameProps extends Record<string, unknown>> = (
  prevProps: CustomFrameProps,
  nextProps: CustomFrameProps
) => boolean;

Props

The custom Frame component that is created with the createFrame helper function allows you to use custom properties:

NameTypeRequiredUsageDescription
CustomFramePropsNocreateFrame helper functionCustomFrameProps allows you to define custom properties for a Frame created using the createFrame helper function. CustomFrameProps should be an object where keys represent the prop names and values represent their types. When using TypeScript, define CustomFrameProps as a type parameter to createFrame for better type safety.

Example of using createFrame

Below is an example demonstrating how to use the createFrame helper function to create a Frame component:

1
2
// This is the UI Kit part of the app, for example, `src/frontend/index.tsx` for a Forge app
import React from 'react';
import ForgeReconciler, { createFrame } from '@forge/react';

// Define the custom properties type for the Frame component,
// this is only applicable to TypeScript-based apps
type TestFrameProps = {
  msg: string
};

// Create a Frame component taking custom props by specifying:
// 1. The target resource key (i.e., the standard resource prop in Frame component)
// 2. The desired custom properties type definitions
//    (only applicable to TypeScript-based app)
const TestFrame = createFrame<CustomFrameProps>('example-frame-resource');

const App = () => {
  return <TestFrame msg="hello" />;
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Communication between UI Kit and Frame

Using custom properties

For many Forge apps, basic communication through properties in a React-like approach is sufficient. The createFrame helper facilitates this by allowing you to pass properties from the UI Kit to the Frame component as simple React component properties, without needing to explicitly use events.emit from the Events API.

The following shows how createFrame can be leveraged to pass properties:

1
2
// This is the UI Kit part of the app, for example, `src/frontend/index.tsx` for a Forge app
import React from 'react';
import ForgeReconciler, { createFrame } from '@forge/react';

// Define the custom properties type for the Frame component,
// this is only applicable to TypeScript-based apps
type CustomFrameProps = {
  msg: string
};

// Create a Frame component with custom props by specifying:
// 1. The target resource key (i.e., the standard resource prop in Frame component)
// 2. The desired custom properties type definitions
//    (only applicable to TypeScript-based app)
const TestFrame = createFrame<CustomFrameProps>('example-frame-resource');

const App = () => {
  return <TestFrame msg="hello" />;
};

ForgeReconciler.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Receiving props in the Frame component using frame.onPropsUpdate

The frame.onPropsUpdate helper allows Frame resources to subscribe to component prop changes, enabling easy consumption of props sent from the UI Kit without needing to explicitly use events.on from the Events API.

Below is an example of how frame.onPropsUpdate can be used:

1
2
// This is the Frame resource part of the app, for example, `resources/frame-app/src/App.tsx` for a Forge app
import React from 'react';
import { view } from "@forge/bridge";

// Define the custom properties type for the Frame component,
// this is only applicable to TypeScript-based apps
type CustomFrameProps = {
  msg: string
};

// Convert `view.frame.onPropsUpdate` to a standard React hook for React-based apps.
const useFrameProps = <TProps,>() => {
  const [frameProps, setFrameProps] = React.useState<TProps | null>(null);
  useEffect(() => {
    let deregister = null;
    const registerPropsUpdate = async () => {
      deregister = await view.frame.onPropsUpdate<TProps>((frameProps) => {
        // Called whenever the props change in the custom Frame component created through `createFrame`.
        setFrameProps(frameProps);
      });
    };

    registerPropsUpdate();
    return () => {
      if (deregister) {
        // Unsubscribe from handling property change events to ensure proper cleanup, as described in the [Events API documentation](https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/events/#unsubscribe).
        deregister();
      }
    };
  }, [setFrameProps]);

  return [frameProps !== null, frameProps];
};

function App() {
  const [isLoaded, frameProps] = useFrameProps<CustomFrameProps>();
  if (!isLoaded) {
    return "Loading...";
  }

  const { msg } = frameProps;
  return <b>{msg}</b>;
}

Tunneling and deploying

To test your app locally with fast iteration, you can use the Forge tunnel to connect your local code with the app running in the development environment

1
2
forge tunnel

To deploy your app, use the Forge deploy command.

1
2
forge deploy

Get started

Supported modules

For the EAP release, support for the Frame component has been enabled in the following modules:

Other modules of the Confluence, Jira, JSM, and Bitbucket products are supported, but since they're part of the Early Access Program (EAP), we haven't fully tested their modules yet, so we can't guarantee complete support at this moment.

ProductModule
Confluence Macro
Global Page
Jira Issue Panel
Global Page
Custom Field
Admin Page
Compass Not Supported

Known limitations and issues

Issues

  • The Custom UI ViewIssueModal does not work within the Frame component for Jira modules. The support for these extension points is currently in progress, and is expected to be available after EAP.

Limitations

  • Only a single Frame component can be rendered per module to minimize potential performance impact.
  • If the Frame component does not resize correctly within the application upon layout updates, we recommend checking and confirming that the sizing properties of the root container are defined using non-viewport-related units such as %, px, em, and so on. This limitation is because the Frame component's root container does not well support viewport-based relative sizing units like vh and vw.

Rate this page: