Plugins
This guide explains how to register, activate, and render plugins in a meeting using the Cloudflare RealtimeKit Core SDK.
Plugins are interactive real-time applications that run inside a meeting, such as a shared whiteboard or a document viewer. When a participant activates a plugin, it becomes active for everyone in the session.
The meeting plugins object is available at meeting.plugins. It exposes two collections of Plugin objects:
all: every plugin available to the local participant.active: the plugins that are currently running in the session.
// All plugins available to youconst allPlugins = meeting.plugins.all.toArray();
// Plugins currently active in the sessionconst activePlugins = meeting.plugins.active.toArray();
// Get a single plugin by its idconst plugin = meeting.plugins.all.get(pluginId);Use the useRealtimeKitSelector hook to read plugins reactively. The hook only works when your component tree is wrapped in RealtimeKitProvider.
import { useRealtimeKitSelector } from "@cloudflare/realtimekit-react";
const allPlugins = useRealtimeKitSelector((m) => m.plugins.all.toArray());const activePlugins = useRealtimeKitSelector((m) => m.plugins.active.toArray());// All plugins available to youval allPlugins = meeting.plugins.all
// Plugins currently active in the sessionval activePlugins = meeting.plugins.active
// Get a single plugin by its idval plugin = meeting.plugins.all.firstOrNull { it.id == pluginId }// All plugins available to youlet allPlugins = meeting.plugins.all
// Plugins currently active in the sessionlet activePlugins = meeting.plugins.active
// Get a single plugin by its idlet plugin = meeting.plugins.all.first { $0.id == pluginId }You register the plugins available in a session when you initialize the SDK. Each configuration provides the metadata RealtimeKit uses to list the plugin and the location it loads.
Pass an array of plugin configurations as defaults.plugins. Each configuration provides the metadata RealtimeKit uses to list the plugin and the component it renders.
RealtimeKitClient.init({ authToken: "<auth_token>", defaults: { plugins: [ { // User-provided unique id. The SDK prefixes it with // `{meetingId}:` to create the namespaced `plugin.id`. id: "whiteboard", // Display name shown in the plugins panel name: "Whiteboard", // Icon URL or data URI shown next to the name icon: "https://example.com/whiteboard.png", // Per-plugin permissions for the local participant permissions: { canActivate: true, canDeactivate: true, }, // The element rendered when the plugin is active component: document.createElement("my-whiteboard"), }, ], },});The component is an HTMLElement. The rtk-plugin-main component projects it into the meeting layout, so your application styles continue to apply.
Each plugin configuration accepts the following fields:
| Field | Description | Type | Required |
|---|---|---|---|
id | Unique identifier for the plugin. The SDK prefixes it with {meetingId}: to form plugin.id. | string | true |
name | Display name shown in the plugins panel. | string | true |
icon | Icon URL or data URI shown next to the name. | string | true |
permissions | Controls whether the local participant can activate or deactivate the plugin. | { canActivate: boolean; canDeactivate: boolean } | true |
component | Element rendered when the plugin is active. | HTMLElement | true |
Pass a pluginConfigs list to RtkMeetingInfo. The SDK loads each plugin's url directly into a WebView when the plugin is activated.
val meetingInfo = RtkMeetingInfo( authToken = "<auth_token>", pluginConfigs = listOf( RtkClientPluginConfig( // User-provided unique id. The SDK prefixes it with // `{meetingId}:` to create the namespaced `plugin.id`. id = "whiteboard", // Display name shown in the plugins list name = "Whiteboard", // Icon URL shown next to the name icon = "https://example.com/whiteboard.png", // URL loaded into the plugin WebView when the plugin is active url = "https://example.com/whiteboard/", // Per-plugin permissions for the local participant permissions = RtkClientPluginPermissions( canActivate = true, canDeactivate = true, ), ), ),)Each plugin configuration accepts the following fields:
| Field | Description | Type | Required |
|---|---|---|---|
id | Unique identifier for the plugin. The SDK prefixes it with {meetingId}: to form plugin.id. | String | true |
name | Display name shown in the plugins list. | String | true |
icon | Icon URL shown next to the name. | String | true |
url | URL loaded into the plugin WebView when the plugin is active. | String | true |
permissions | Controls whether the local participant can activate or deactivate the plugin. | RtkClientPluginPermissions | true |
Pass a pluginConfigs array to RtkMeetingInfo. The SDK loads each plugin's url directly into a WebView when the plugin is activated.
let meetingInfo = RtkMeetingInfo( authToken: "<auth_token>", pluginConfigs: [ RtkClientPluginConfig( // User-provided unique id. The SDK prefixes it with // `{meetingId}:` to create the namespaced `plugin.id`. id: "whiteboard", // Display name shown in the plugins list name: "Whiteboard", // Icon URL shown next to the name icon: "https://example.com/whiteboard.png", // URL loaded into the plugin WebView when the plugin is active url: "https://example.com/whiteboard/", // Per-plugin permissions for the local participant permissions: RtkClientPluginPermissions( canActivate: true, canDeactivate: true ) ) ])Each plugin configuration accepts the following fields:
| Field | Description | Type | Required |
|---|---|---|---|
id | Unique identifier for the plugin. The SDK prefixes it with {meetingId}: to form plugin.id. | String | true |
name | Display name shown in the plugins list. | String | true |
icon | Icon URL shown next to the name. | String | true |
url | URL loaded into the plugin WebView when the plugin is active. | String | true |
permissions | Controls whether the local participant can activate or deactivate the plugin. | RtkClientPluginPermissions | true |
Activation lives on the Plugin object. Calling activate() enables the plugin for every participant in the session, and deactivate() disables it for everyone. Both methods respect the plugin's permissions.
const plugin = meeting.plugins.all.get(pluginId);
// Activate for all participantsawait plugin.activate();
// Deactivate for all participantsawait plugin.deactivate();const plugins = useRealtimeKitSelector((m) => m.plugins);
const plugin = plugins.all.get(pluginId);
// Activate for all participantsawait plugin.activate();
// Deactivate for all participantsawait plugin.deactivate();val plugin = meeting.plugins.all.firstOrNull { it.id == pluginId } ?: return
// Activate for all participantsplugin.activate()
// Deactivate for all participantsplugin.deactivate()guard let plugin = meeting.plugins.all.first(where: { $0.id == pluginId }) else { return }
// Activate for all participantsplugin.activate()
// Deactivate for all participantsplugin.deactivate()A Plugin object represents a single plugin. You obtain it from either collection in meeting.plugins.
| Property | Description | Type |
|---|---|---|
id | Namespaced plugin id, in the form {meetingId}:{configId}. | string |
name | Display name of the plugin. | string |
icon | Icon URL or data URI. | string |
permissions | Activation permissions for the local participant. | { canActivate: boolean; canDeactivate: boolean } |
component | Element rendered when the plugin is active. | HTMLElement |
active | Whether the plugin is currently running. | boolean |
enabledBy | Id of the participant who activated the plugin. | string |
| Property | Description | Type |
|---|---|---|
id | Namespaced plugin id, in the form {meetingId}:{configId}. | String |
name | Display name of the plugin. | String |
icon | Icon URL. | String |
permissions | Activation permissions for the local participant. | RtkClientPluginPermissions |
While a plugin is active, call getPluginView() to obtain the Android WebView that hosts it, and sendData(eventName, data) to push data into that WebView.
| Property | Description | Type |
|---|---|---|
id | Namespaced plugin id, in the form {meetingId}:{configId}. | String |
name | Display name of the plugin. | String |
icon | Icon URL. | String |
permissions | Activation permissions for the local participant. | RtkClientPluginPermissions |
While a plugin is active, call getPluginView() to obtain the WKWebView that hosts it, and sendData(eventName:data:) to push data into that WebView.
A Plugin object emits events as its state changes. You can listen on a single plugin, or on a map to receive events for every plugin it contains.
| Event | Description |
|---|---|
stateUpdate | Emitted when the plugin is activated or deactivated. |
enabled | Emitted when the plugin becomes active for the local participant. |
closed | Emitted when the plugin is deactivated for the local participant. |
ready | Emitted when the plugin is ready to use. |
const plugin = meeting.plugins.all.get(pluginId);
plugin.on("stateUpdate", ({ active, pluginId }) => { console.log(`Plugin ${pluginId} active:`, active);});
// Listen for any plugin being added to or removed from the mapmeeting.plugins.all.on("pluginAdded", (plugin) => { console.log("Plugin added:", plugin.name);});
meeting.plugins.all.on("pluginDeleted", (plugin) => { console.log("Plugin removed:", plugin.name);});Register an RtkPluginsEventListener to receive plugin events.
| Callback | Description |
|---|---|
onPluginActivated | Called when a plugin is activated for all participants. |
onPluginDeactivated | Called when a plugin is deactivated for all participants. |
onPluginMessage | Called when a plugin sends a message to the app. |
onPluginFileRequest | Called when a plugin requests a file from the app. |
val pluginsEventListener = object : RtkPluginsEventListener { override fun onPluginActivated(plugin: RtkPlugin) { // A plugin became active for all participants }
override fun onPluginDeactivated(plugin: RtkPlugin) { // A plugin was deactivated for all participants }
override fun onPluginMessage(plugin: RtkPlugin, eventName: String, data: Any?) { // A plugin sent a message to the app }
override fun onPluginFileRequest(plugin: RtkPlugin) { // A plugin requested a file from the app }}
meeting.addPluginsEventListener(pluginsEventListener)Conform to RtkPluginsEventListener and register the listener to receive plugin events.
| Callback | Description |
|---|---|
onPluginActivated | Called when a plugin is activated for all participants. |
onPluginDeactivated | Called when a plugin is deactivated for all participants. |
onPluginMessage | Called when a plugin sends a message to the app. |
onPluginFileRequest | Called when a plugin requests a file from the app. |
extension MeetingViewModel: RtkPluginsEventListener { func onPluginActivated(plugin: RtkPlugin) { // A plugin became active for all participants }
func onPluginDeactivated(plugin: RtkPlugin) { // A plugin was deactivated for all participants }
func onPluginMessage(plugin: RtkPlugin, eventName: String, data: Any?) { // A plugin sent a message to the app }
func onPluginFileRequest(plugin: RtkPlugin) { // A plugin requested a file from the app }}
meeting.addPluginsEventListener(self)If you use the UI Kit, RealtimeKit provides ready-made components for plugins:
rtk-plugins-toggle: a control bar button that opens and closes the plugins sidebar.rtk-plugins: a list of available plugins with controls to activate or deactivate each one.rtk-plugin-main: renders thecomponentof an active plugin in the meeting layout.
These components read from meeting.plugins, so they reflect plugin state automatically once you register your plugins at initialization.
When a plugin is active, getPluginView() returns the Android WebView that hosts it. Add this view to your layout to display the plugin.
val plugin = meeting.plugins.active.firstOrNull() ?: return
// Returns an Android WebView you can add to your layoutval pluginView = plugin.getPluginView()When a plugin is active, getPluginView() returns the WKWebView that hosts it. Add this view to your view hierarchy to display the plugin.
guard let plugin = meeting.plugins.active.first else { return }
// Returns a WKWebView you can add to your view hierarchylet pluginView = plugin.getPluginView()