> ## Documentation Index
> Fetch the complete documentation index at: https://botpress-pb-update-api.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native

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

<Note>
  **Why a WebView?** Botpress Cloud Webchat is a **browser** client (`window.botpress`). React Native does not run that API natively, so the supported pattern is to run the official Webchat **inside a WebView** and communicate with `postMessage` and `injectJavaScript`.

  The [`@botpress/webchat`](/webchat/react-library/get-started) package targets web React. For native apps, use the WebView approach below.
</Note>

## Prerequisites

<Info>
  You will need:

  * A [published bot](/get-started/quick-start)
  * A React Native or Expo project
  * [`react-native-webview`](https://github.com/react-native-webview/react-native-webview)
</Info>

## 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**:

<Frame>
  <img alt="Embed code from Dashboard" className="block dark:hidden" src="https://mintcdn.com/botpress-pb-update-api/oD2ntPQ5dxiZJE1J/snippets/webchat/assets/embed-code-dashboard.png?fit=max&auto=format&n=oD2ntPQ5dxiZJE1J&q=85&s=a3d5e81d0ca9e72e067829b397e70c21" width="1600" height="288" data-path="snippets/webchat/assets/embed-code-dashboard.png" />

  <img alt="Embed code from Dashboard" className="hidden dark:block" src="https://mintcdn.com/botpress-pb-update-api/oD2ntPQ5dxiZJE1J/snippets/webchat/assets/embed-code-dashboard-dark.png?fit=max&auto=format&n=oD2ntPQ5dxiZJE1J&q=85&s=4867bda5552c06a1e0415322ba2f0897" width="1600" height="288" data-path="snippets/webchat/assets/embed-code-dashboard-dark.png" />
</Frame>

## 2. Lay out your project files

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

<Tree>
  <Tree.Folder name="src" defaultOpen>
    <Tree.Folder name="botpress" defaultOpen>
      <Tree.File name="getBotpressWebchat.js" />

      <Tree.File name="BpWidget.js" />

      <Tree.File name="BpWidget.d.ts" />
    </Tree.Folder>

    <Tree.Folder name="config" defaultOpen>
      <Tree.File name="botpressConfig.js" />
    </Tree.Folder>
  </Tree.Folder>
</Tree>

| Path                                     | Purpose                                                                                                       |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **`src/botpress/getBotpressWebchat.js`** | Builds `{ html, baseUrl }` for the WebView.                                                                   |
| **`src/botpress/BpWidget.js`**           | WebView 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.js`**       | Webchat 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`:

```json theme={null}
{
  "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:

<CodeGroup>
  ```bash Expo theme={null}
  npx expo install react-native-webview
  ```

  ```bash npm theme={null}
  npm install react-native-webview
  ```

  ```bash yarn theme={null}
  yarn add react-native-webview
  ```

  ```bash pnpm theme={null}
  pnpm add react-native-webview
  ```
</CodeGroup>

<Note>
  For bare React Native, follow [react-native-webview linking](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Getting-Started.md) if your setup requires it. Expo users can also see the [Expo WebView docs](https://docs.expo.dev/versions/latest/sdk/webview/).
</Note>

## 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`.

<Note>
  **Sensitive values:** `botId` and embed URLs identify your bot. For a public repository, do not commit real values, use placeholders in git or keep this file out of version control.
</Note>

```javascript theme={null}
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 }} />`.

```javascript getBotpressWebchat.js theme={null}
/**
 * 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.

```javascript BpWidget.js expandable theme={null}
/**
 * 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={["*"]}
    />
  );
}
```

<Note>
  **`onMessage` format:** `event.nativeEvent.data` is a **string**. Parse with `JSON.parse`. Shape: **`{ event: string, data: unknown }`**.
</Note>

## 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:

```typescript theme={null}
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/…`**).

```tsx ChatScreen.tsx theme={null}
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

```bash theme={null}
npm install
npx expo start
```

<Check>
  Open the iOS Simulator, Android emulator, or a physical device from there. Your bot should load inside the WebView when you run the app.
</Check>

## Troubleshooting

| Problem                                  | What to check                                                                                                                                                                    |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Blank WebView                            | **`embedScriptUrl`** in **`botpressConfig.js`** must be the **`files.bpcontent.cloud/...`** URL from Studio.                                                                     |
| Error: `embedScriptUrl is required`      | Set `embedScriptUrl` in `botpressConfig`.                                                                                                                                        |
| Close (X) opens the Botpress.com website | Keep **`onShouldStartLoadWithRequest`** and **`closeChatJs`** in `BpWidget.js` as shown in that section.                                                                         |
| Fonts look wrong                         | Tune appearance in **Botpress Studio**; avoid forcing global `font-family` in the HTML shell.                                                                                    |
| Android keyboard covers the composer     | In **`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](/webchat/get-started/configure-your-webchat) it in Studio to match your product. For more control over the embed, see [Embed Webchat](/webchat/get-started/embedding-webchat) and the [Webchat interaction reference](/webchat/interact/reference).
