React Native

Add your bot in any React Native or Expo app using react-native-webview.

Prerequisites

1. Copy your Webchat embed code from the dashboard

Before you write any app code, grab the same embed snippet you would use on a website. In the Botpress dashboard:

  1. Open your bot’s Workspace and select the bot you want to embed.
  2. In the left sidebar, go to Webchat > Deploy Settings.
  3. Copy the Embed code:
Embed code from Dashboard

2. Lay out your project files

Create a src folder at your project root if you do not already have one. A typical layout:

src
botpress
getBotpressWebchat.js
BpWidget.js
BpWidget.d.ts
config
botpressConfig.js
PathPurpose
src/botpress/getBotpressWebchat.jsBuilds { html, baseUrl } for the WebView.
src/botpress/BpWidget.jsWebView component and event bridge (onMessage); blocks marketing site navigation on close.
src/botpress/BpWidget.d.ts(Optional, TypeScript only) Types for BpWidget.
src/config/botpressConfig.jsWebchat URLs and optional botId. Step 4 shows what goes inside export const botpressConfig = { ... }.

You can instead keep files flat under src/ and use relative imports. The examples below assume src/botpress/ and src/config/.

Path alias (optional, Expo / TypeScript): In tsconfig.json, merge into compilerOptions:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Then imports like import BpWidget from '@/botpress/BpWidget' resolve. If you skip aliases, use relative imports only.

3. Install react-native-webview

From your project root:

npx expo install react-native-webview
npm install react-native-webview
yarn add react-native-webview
pnpm add react-native-webview

4. Save your Studio script URLs in one config file

Create src/config/botpressConfig.js and export a single botpressConfig object. Paste the script URLs from Studio > Webchat > Embed (the same values from your embed snippet). The HTML helper and the widget only read botConfig: you pass it in, and you do not duplicate URLs inside getBotpressWebchat.js or BpWidget.js.

export const botpressConfig = {
  botId: 'YOUR_BOT_ID',
  injectScriptUrl: 'https://cdn.botpress.cloud/webchat/v3.6/inject.js',
  embedScriptUrl: 'https://files.bpcontent.cloud/YOUR/PATH/YOUR-GENERATED.js',
  baseUrl: 'https://cdn.botpress.cloud/',
}

5. Build the HTML page that loads Webchat

Add src/botpress/getBotpressWebchat.js. It builds a tiny HTML page (inject script, embed script, full height container) and returns { html, baseUrl } so you can pass it straight into <WebView source={{ baseUrl, html }} />.

/**
 * Builds the HTML page loaded by the WebView for Botpress Cloud webchat v3.
 * @param {Record<string, unknown>} botConfig Pass `botpressConfig` from src/config/botpressConfig.js
 * @returns {{ html: string, baseUrl: string }}
 */
const getBotpressWebchat = (botConfig) => {
  // Official inject script (Botpress loader); falls back to a known default if omitted.
  const injectUrl = botConfig.injectScriptUrl || 'https://cdn.botpress.cloud/webchat/v3.6/inject.js'
  // Studio-generated bundle URL (required). This wires your bot into the page.
  const embedUrl = botConfig.embedScriptUrl
  if (!embedUrl || typeof embedUrl !== 'string') {
    throw new Error('botConfig.embedScriptUrl is required (paste the second script URL from Studio embed)')
  }
  // Origin passed to WebView as `baseUrl` so relative URLs in the HTML resolve correctly.
  const baseUrl = botConfig.baseUrl || 'https://cdn.botpress.cloud/'

  // Minimal HTML shell: viewport, full-height container, then inject + embed scripts (matches Studio order).
  const html = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <style>
      html, body { margin: 0; height: 100%; }
      body {
        display: flex;
        flex-direction: column;
        height: 100vh;
      }
      #bp-web-widget-container {
        height: 100%;
        width: 100%;
        flex: 1;
      }
    </style>
    <title>Chatbot</title>
  </head>
  <body>
    <script src="${injectUrl}"></script>
    <script src="${embedUrl}" defer></script>
  </body>
</html>`

  // WebView expects this pair: `source={{ baseUrl, html }}`.
  return { baseUrl, html }
}

module.exports = getBotpressWebchat

6. Show Webchat in a WebView and forward events to React Native

Create src/botpress/BpWidget.js. It renders a WebView that loads your HTML, then relays what happens inside the page to your React Native layer:

  • Puts the page from getBotpressWebchat(botConfig) into the WebView.
  • Injects a script that subscribes to window.botpress and sends each event to React Native with postMessage (you read them in onMessage as JSON strings).
  • Intercepts navigation to botpress.com or *.botpress.com when the user closes the widget and runs window.botpress.close() instead to close the webchat interface.
/**
 * WebView wrapper for Botpress webchat v3: window.botpress API, events to React Native via postMessage.
 */
import { WebView } from 'react-native-webview'
import getBotpressWebchat from './getBotpressWebchat'
import React, { useCallback, useRef } from 'react'

const broadcastToReactNative = `
(function () {
  // Sends one event payload to React Native as a JSON string (read in onMessage).
  function post(ev, data) {
    try {
      window.ReactNativeWebView.postMessage(JSON.stringify({ event: ev, data: data }));
    } catch (e) {}
  }
  // Subscribes to window.botpress once it exists and mirrors events into React Native.
  function wire() {
    if (!window.botpress || typeof window.botpress.on !== "function") return false;
    window.botpress.on("webchat:initialized", function () {
      try { window.botpress.open(); } catch (e) {}
    });
    [
      "message",
      "webchat:initialized",
      "webchat:ready",
      "webchat:opened",
      "webchat:closed",
      "customEvent",
      "error",
      "conversation",
    ].forEach(function (ev) {
      window.botpress.on(ev, function (data) {
        post(ev, data);
      });
    });
    // Fallback open in case the widget did not auto-open after init.
    setTimeout(function () {
      try { window.botpress.open(); } catch (e) {}
    }, 400);
    return true;
  }
  // Poll until botpress is ready (or give up after ~12s) so inject order does not race.
  var n = 0;
  var id = setInterval(function () {
    if (wire() || ++n > 240) clearInterval(id);
  }, 50);
})();
true;
`

const closeChatJs = `
try {
  // Programmatic close when we block a navigation attempt (through onShouldStartLoadWithRequest).
  if (window.botpress && typeof window.botpress.close === "function") {
    window.botpress.close();
  }
} catch (e) {}
true;
`

// Returns true when a navigation URL is the public Botpress marketing site (close button behavior).
function isBotpressMarketingSiteUrl(url) {
  if (!url || url.startsWith('about:') || url.startsWith('blob:') || url.startsWith('data:')) {
    return false
  }
  try {
    const h = new URL(url).hostname
    return h === 'botpress.com' || h.endsWith('.botpress.com')
  } catch {
    return false
  }
}

export default function BpWidget(props) {
  const { botConfig, onMessage } = props
  const webref = useRef(null)

  // Build the in-memory HTML page and origin for this WebView instance.
  const { html, baseUrl } = getBotpressWebchat(botConfig)

  // Intercept navigations: block opening botpress.com inside the frame and close chat instead.
  const onShouldStartLoadWithRequest = useCallback((request) => {
    const { url } = request
    if (isBotpressMarketingSiteUrl(url)) {
      webref.current?.injectJavaScript(closeChatJs)
      return false
    }
    return true
  }, [])

  // Webchat needs JavaScript and DOM storage; originWhitelist allows loading scripts from Botpress CDNs.
  return (
    <WebView
      ref={webref}
      style={{ flex: 1 }}
      source={{
        baseUrl,
        html,
      }}
      onMessage={onMessage}
      injectedJavaScript={broadcastToReactNative}
      onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
      setSupportMultipleWindows={false}
      javaScriptEnabled
      domStorageEnabled
      originWhitelist={['*']}
    />
  )
}

7. Optional: add TypeScript types for the widget

If you use TypeScript, add src/botpress/BpWidget.d.ts next to BpWidget.js so imports and props are typed:

import type { FunctionComponent } from 'react'

/** Props for the WebView wrapper: Studio-derived config and optional React Native message handler. */
export interface BpWidgetProps {
  botConfig: Record<string, unknown>
  onMessage?: (e: { nativeEvent: { data: string } }) => void
}

/** Default export from BpWidget.js (JavaScript component with these props). */
declare const BpWidget: FunctionComponent<BpWidgetProps>

export default BpWidget

8. Render the Webchat widget on a screen

Import BpWidget and botpressConfig, then render it full screen or inside any View that uses flex: 1. Adjust import paths to your real paths (for example Expo Router app/index.tsx, a root App.js, or src/screens/…).

import { StyleSheet, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'

import BpWidget from '@/botpress/BpWidget'
import { botpressConfig } from '@/config/botpressConfig'

/** Screen that fills the safe area with the Botpress WebView-powered widget. */
export default function ChatScreen() {
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.main}>
        <BpWidget
          botConfig={botpressConfig}
          onMessage={(e) => {
            // Bridge: WebView posts JSON strings; parse to `{ event, data }` for logging or app logic.
            const raw = e.nativeEvent?.data
            if (!raw) return
            try {
              const msg = JSON.parse(raw)
              console.log(msg.event, msg.data)
            } catch {
              /* non-JSON */
            }
          }}
        />
      </View>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  safeArea: { flex: 1 },
  main: { flex: 1 },
})

9. Run your app

npm install
npx expo start

Troubleshooting

ProblemWhat to check
Blank WebViewembedScriptUrl in botpressConfig.js must be the files.bpcontent.cloud/... URL from Studio.
Error: embedScriptUrl is requiredSet embedScriptUrl in botpressConfig.
Close (X) opens the Botpress.com websiteKeep onShouldStartLoadWithRequest and closeChatJs in BpWidget.js as shown in that section.
Fonts look wrongTune appearance in Botpress Studio; avoid forcing global font-family in the HTML shell.
Android keyboard covers the composerIn app.json (Expo), under expo.android, set "softwareKeyboardLayoutMode": "resize". Rebuild the native Android app after changing this (not only a Metro refresh).

Next steps

Now that Webchat runs in your app, try styling it in Studio to match your product. For more control over the embed, see Embed Webchat and the Webchat interaction reference.