Plugins·续
override fun onPluginFileRequest(plugin: RtkPlugin) { // A plugin requested a file from the app } }
meeting.addPluginsEventListener(pluginsEventListener)
// Remove the listener when the owning screen is torn down meeting.removePluginsEventListener(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's web page sends a message to the app. Refer to [Exchange messages with a plugin](#exchange-messages-with-a-plugin). |
| `onPluginFileRequest` | Called when a plugin requests a file from the app. |
```swift
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(pluginsEventListener: self)
// Remove the listener when the owning screen is torn down
meeting.removePluginsEventListener(pluginsEventListener: self)
Render plugins
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 from the onPluginActivated callback of your plugins event listener.
The first call to getPluginView() after activation creates the WebView and starts loading the plugin's url. Later calls return the same view while the plugin stays active. The method returns null while the plugin is inactive. When the plugin is deactivated, the SDK discards its WebView, so remove the view from your layout and call getPluginView() again after the plugin is reactivated.
// In your RtkPluginsEventListener
override fun onPluginActivated(plugin: RtkPlugin) {
runOnUiThread {
// Returns an Android WebView you can add to your layout
val pluginView = plugin.getPluginView() ?: return@runOnUiThread
pluginContainer.addView(pluginView)
}
}
override fun onPluginDeactivated(plugin: RtkPlugin) {
runOnUiThread {
// The SDK discards the WebView on deactivation
pluginContainer.removeAllViews()
}
}
When a plugin is active, getPluginView() returns the WKWebView that hosts it. Add this view to your view hierarchy from the onPluginActivated callback of your plugins event listener.
The first call to getPluginView() after activation creates the WebView and starts loading the plugin's url. Later calls return the same view while the plugin stays active. The method returns nil while the plugin is inactive. When the plugin is deactivated, the SDK discards its WebView, so remove the view from your view hierarchy and call getPluginView() again after the plugin is reactivated.
// In your RtkPluginsEventListener
func onPluginActivated(plugin: RtkPlugin) {
DispatchQueue.main.async {
// Returns a WKWebView you can add to your view hierarchy
guard let pluginView = plugin.getPluginView() else { return }
self.pluginContainer.addSubview(pluginView)
}
}
func onPluginDeactivated(plugin: RtkPlugin) {
DispatchQueue.main.async {
// The SDK discards the WebView on deactivation
self.pluginContainer.subviews.forEach { $0.removeFromSuperview() }
}
}
The React Native UI Kit provides ready-made components for plugins:
RtkPluginsToggle: a control bar button that opens and closes the plugins sidebar.RtkPlugins: a panel listing available plugins with controls to activate or deactivate each one.RtkPluginMain: renders the active plugin in aWebView.
These components read from meeting.plugins automatically once plugins are registered at initialization. If you use RtkMeeting, plugin UI is included without any additional setup.
Exchange messages with a plugin
On web, a plugin's component is an HTMLElement that runs in your own page, so RealtimeKit does not provide a separate messaging API. Exchange data with the plugin using the mechanisms your UI framework provides, such as properties, attributes, callbacks, or DOM events.
If your plugin component embeds an iframe, send messages to it with iframe.contentWindow.postMessage(message, targetOrigin), and listen for replies with a message event listener on window. Always pass the plugin's origin as targetOrigin, and check event.origin before you trust an incoming message.
Messaging between the app and a plugin through sendData and onPluginMessage is available in the Android and iOS Core SDKs. Select Android or iOS in the SDK selector to view this section.
On Android and iOS, a plugin is a web page that the SDK loads into a WebView. Your app and the page exchange messages through a bridge that the SDK injects into the WebView. The bridge has two independent, one-way directions. It does not pair messages into requests and responses.
| Direction | App API | Message type |
Plugin page API |
|---|---|---|---|
| App → plugin | RtkPlugin.sendData(eventName, data) |
24 |
window.addEventListener("message", handler) |
| Plugin → app | RtkPluginsEventListener.onPluginMessage(plugin, eventName, data) |
20 |
window.parent.postMessage(message, "*") |
Each message carries a string event name and optional data. Event names are your own application convention; the SDK does not reserve any. Data must be JSON-compatible: null, strings, numbers, booleans, lists, and maps with string keys.
Messages travel only between your app and the plugin WebView on the same device. They are not sent to other participants. To share plugin state across participants, use the meeting key-value store or message broadcast.
The bridge does not authenticate the page that sends messages. Only register plugin URLs that you trust, and validate every message you receive on either side.
Receive messages from the plugin
The SDK calls onPluginMessage on every registered RtkPluginsEventListener when the plugin page sends a message. Implement it in the listener you registered in Listen to plugin events. The plugin must be active, and the listener must be registered before the page sends the message.
override fun onPluginMessage(plugin: RtkPlugin, eventName: String, data: Any?) {
when (eventName) {
"ready" -> {
// The plugin page has loaded and is listening for messages
plugin.sendData("init", mapOf("theme" to "dark"))
}
"score.updated" -> {
val payload = data as? Map<*, *> ?: return
val score = payload["score"] as? Int ?: return
// Update your app with score
}
}
}
The SDK converts the JSON data sent by the page to Kotlin values:
| JSON value | Kotlin type |
|---|---|
| Object | Map<String, Any?> |
| Array | List<Any?> |
| String | String |
| Boolean | Boolean |
| Number | Int, Long, or Double |
null or omitted |
null |
func onPluginMessage(plugin: RtkPlugin, eventName: String, data: Any?) {
switch eventName {
case "ready":
// The plugin page has loaded and is listening for messages
plugin.sendData(eventName: "init", data: ["theme": "dark"])
case "score.updated":
guard let payload = data as? [String: Any],
let score = payload["score"] as? NSNumber else { return }
// Update your app with score
default:
break
}
}
The SDK converts the JSON data sent by the page to Foundation values. Cast objects with as? [String: Any], arrays with as? [Any], strings with as? String, and numbers or booleans with as? NSNumber. If the page sends null or omits data, data is nil.
Send messages to the plugin
Call sendData on an active RtkPlugin to send an event to its web page.
val error = plugin.sendData(
eventName = "init",
data = mapOf("theme" to "dark", "locale" to "en-US"),
)
if (error != null) {
// PluginError.NotActive: the plugin is not active
}
if let error = plugin.sendData(eventName: "init", data: ["theme": "dark", "locale": "en-US"]) {
// PluginError.NotActive: the plugin is not active
print(error)
}
sendData returns PluginError.NotActive if the plugin is not active. Otherwise it returns no error and delivers the message asynchronously. A missing error means the SDK accepted the message, not that the page received it. The SDK drops the message without reporting an error when:
- You have not called
getPluginView()since the plugin was activated, so no WebView exists. - The page has not loaded yet, or it has not registered its
messagelistener. datacontains a value that is not JSON-compatible.
The SDK does not queue messages or acknowledge delivery. To avoid sending before the page is ready, have the page send a ready event after it loads, and call sendData only after onPluginMessage receives that event. The examples on this page follow this pattern.
Receive messages in the plugin
In the plugin page, listen for message events on window. The SDK delivers each message as an object with this shape:
{
"type": 24,
"uuid": "",
"payload": {
"eventName": "init",
"data": { "theme": "dark", "locale": "en-US" }
}
}
Register the listener in an inline script in the page <head> so that it is installed before your app sends anything:
<script>
window.addEventListener("message", (event) => {
const message = event.data;
// Ignore anything that is not a message from the app
if (!message || message.type !== 24 || !message.payload) return;
const { eventName, data } = message.payload;
if (eventName === "init") {
// Validate data before using it
document.body.dataset.theme = data?.theme ?? "light";
}
});
</script>
event.data is already an object. Do not call JSON.parse on it.
Send messages from the plugin
To send a message to the app, call window.parent.postMessage with an object that has type: 20 and a payload object containing a string eventName and optional data.
The SDK replaces window.parent.postMessage in the WebView each time a page starts loading, but it does not signal when the replacement is in place. Send your first message after the page's load event instead of from a script that runs while the page is parsed.
function sendToApp(eventName, data) {
window.parent.postMessage({ type: 20, payload: { eventName, data } }, "*");
}
// Tell the app that the page is ready to receive messages
window.addEventListener("load", () => sendToApp("ready"));
// Send application events after the page has loaded
document.querySelector("#submit-score").addEventListener("click", () => {
sendToApp("score.updated", { score: 42 });
});
The SDK ignores the second argument to postMessage. It does not restrict which origins can send messages. The app does not receive messages that use a different type, omit payload, or are sent while the plugin is inactive. If you need to correlate a reply with a request, include your own identifier in data.
Caution
Pass the message as an object. Do not call JSON.stringify on it first: the bridge serializes the object itself, and a pre-serialized string or an eventName that is not a string can cause an uncaught exception in the native SDK and crash your app.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/plugins/#page","headline":"Plugins","description":"Register and control plugins in RealtimeKit meetings.","url":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/plugins/","inLanguage":"en","image":"https://developers.cloudflare.com/realtime/realtimekit/humans/core/plugins/og.png?v=84931cceda75276f","dateModified":"2026-10-26","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Create, vote on, and manage polls in RealtimeKit meetings using the Core SDK. title: Polls image: https://developers.cloudflare.com/realtime/realtimekit/humans/core/polls/og.png?v=7b492c0f3fe879c6
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/realtime/llms.txt
Use this file to discover all available pages before exploring further.
Docs