Forge Realtime is now available as an Early Access Program (EAP). To start testing this feature, sign up using this form. This feature is currently only available in Jira, with support for other products to be added in future.
APIs and features under EAP are unsupported and subject to change without notice. APIs and features under EAP are not recommended for use in production environments.
For more details, see Forge EAP, Preview, and GA.
Forge Realtime allows Forge apps to subscribe and publish to realtime channels. Unlike the existing Events API, realtime events are broadcast beyond the current window. This means that it can be used for scenarios where live updates need to propagate to multiple users.
Realtime events can also be published from resolvers, eliminating the need for the frontend to poll the resolver for live updates. However, resolvers cannot subscribe to channels.
The subscribe() function allows you to subscribe to a channel and call a provided callback function when any event is published to the channel. This includes events published from both the frontend and from resolvers.
Subscriptions created with the subscribe() function are scoped to channels for the current module context by default. This means that a subscription in a module, for example jira:issuePanel, will only receive messages that are published from the same module in the same Jira issue. It will not receive messages from a jira:issueContext on that issue, or the jira:issuePanel on a different issue, even if they share the same channel name.
See the in-depth guide on how to authorize channels for more detail.
1 2const subscribe = ( channel: string, callback: (payload?: string) => any, options?: SubscriptionOptions ): Promise<Subscription> interface SubscriptionOptions { replaySeconds?: number; token?: string; contextOverrides?: ProductContext[]; }
1 2export enum Jira { Board = 'board', Issue = 'issue', Project = 'project' } type ProductContext = Jira;
channel parameter in the corresponding publish() function.payload, that is called when an event is received on the channel.Subscription which contains the unsubscribe() function.1 2import { realtime } from '@forge/bridge'; import { Button } from '@forge/react'; const App = () => { return ( <Button onClick={() => { const onEvent = (payload: string) => { console.log('Received event with payload: ', payload); }; const subscription = realtime.subscribe('my-test-channel', onEvent); }}> Subscribe </Button> ); };
The subscribeGlobal() function allows you to subscribe to a global channel and call a provided callback function when any event is published to the channel. This includes events published from both the frontend and from resolvers.
Global channels are not secured by the Atlassian app context that the user has permissions for.
If an app is publishing messages to a global channel with publishGlobal(), then any user with access to the app installation can subscribe to that channel with subscribeGlobal() and receive its messages if they know the channel name. This is the case even if the message originates from a page that the user does not have permissions for, like a restricted Jira issue or Confluence page.
It is your responsibility to ensure you are scoping your channels appropriately, and only using global channels if absolutely necessary. Using channel tokens to enforce Atlassian app permissions is also encouraged when using global channels.
1 2const subscribeGlobal = ( channel: string, callback: (payload?: string) => any, options?: SubscriptionOptions ): Promise<Subscription> interface SubscriptionOptions { replaySeconds?: number; token?: string; contextOverrides?: ProductContext[]; }
1 2export enum Jira { Board = 'board', Issue = 'issue', Project = 'project' } type ProductContext = Jira;
channel parameter in the corresponding publishGlobal() function.payload, that is called when an event is received on the channel.Subscription which contains the unsubscribe() function.1 2import { realtime } from '@forge/bridge'; import { Button } from '@forge/react'; const App = () => { return ( <Button onClick={() => { const onEvent = (payload: string) => { console.log('Received event with payload: ', payload); }; const subscription = realtime.subscribeGlobal('my-test-channel', onEvent); }}> Subscribe </Button> ); };
The unsubscribe() function for a channel can be accessed through the Subscription object that was returned from the subscribe() function for that channel.
Calling Subscription.unsubscribe() will remove the channel subscription for the specific Subscription instance and will not globally remove all subscriptions from that channel.
1 2type Subscription = { unsubscribe: () => void; };
1 2import { useEffect } from 'react'; import { realtime } from '@forge/bridge'; const App = () => { useEffect(() => { const onEvent = (payload: string) => { console.log('Received event with payload: ', payload); }; const subscription = realtime.subscribe('my-test-channel', onEvent); const globalSubscription = realtime.subscribeGlobal( 'my-test-global-channel', onEvent ); return () => { subscription.then(s => s.unsubscribe()); globalSubscription.then(s => s.unsubscribe()); } }, []); return ( <div>My App</div> ); };
The publish() function will publish an event to the channel with the given payload. If you have subscribed to the channel with the subscribe() function, your callback will be invoked with the event. The payload must be a string, so make sure to stringify any objects before passing them as arguments.
1 2const publish = ( channel: string, payload: string, options?: PublishOptions ): Promise<PublishResult> interface PublishOptions { token?: string; contextOverrides?: ProductContext[]; } interface PublishResult { eventId: string | null; eventTimestamp: string | null; errors?: string[]; }
1 2export enum Jira { Board = 'board', Issue = 'issue', Project = 'project' } type ProductContext = Jira;
channel parameter in the corresponding subscribe() function.PublishResult which contains information about the published event.
1 2import { useEffect } from 'react'; import { realtime } from '@forge/bridge'; import { Button } from '@forge/react'; const App = () => { useEffect(() => { const onEvent = (payload: string) => { console.log('Received event with payload: ', payload); }; const subscription = realtime.subscribe('my-test-channel', onEvent); return () => { subscription.then(s => s.unsubscribe()); } }, []); return ( <Button onClick={() => realtime.publish('my-test-channel', 'Here is an event payload!')}> Publish event </Button> ); };
The publishGlobal() function will publish an event to the channel with the given payload. If you have subscribed to the channel with the subscribeGlobal() function, your callback will be invoked with the event. The payload must be a string, so make sure to stringify any objects before passing them as arguments.
1 2const publishGlobal = ( channel: string, payload: string, options?: PublishOptions ): Promise<PublishResult> interface PublishOptions { token?: string; contextOverrides?: ProductContext[]; } interface PublishResult { eventId: string | null; eventTimestamp: string | null; }
1 2export enum Jira { Board = 'board', Issue = 'issue', Project = 'project' } type ProductContext = Jira;
channel parameter in the corresponding subscribe() function.PublishResult which contains information about the published event.
1 2import { useEffect } from 'react'; import { realtime } from '@forge/bridge'; import { Button } from '@forge/react'; const App = () => { useEffect(() => { const onEvent = (payload: string) => { console.log('Received event with payload: ', payload); }; const subscription = realtime.subscribeGlobal('my-test-channel', onEvent); return () => { subscription.then(s => s.unsubscribe()); } }, []); return ( <Button onClick={() => realtime.publishGlobal('my-test-channel', 'Here is an event payload!')}> Publish global event </Button> ); };
Rate this page: