Web Integration

React Native Plugin 4.x > Web Integration

👍

While reading the documentation, take a look at our Sample App.

Overview

Web Integration is a second integration mode of the React Native plugin (alongside Classic). The difference: the publisher owns and renders their own react-native-webview, and the plugin attaches the Taboola native ↔ JS bridge onto it — the SDK never creates or controls the WebView.

Use Web Integration when:

  • You already ship a WebView-driven experience and want to place Taboola units inside your own page.
  • You need to author the Taboola tag directly into your page HTML (loader + placement containers + _taboola.push config).
  • You want the auto-fetch flow (lazyFetch: false) — the Mobile Loader fetches automatically once your content page loads.
🚧

New Architecture only

Registration is driven by a Fabric command (registerWebView) on TBLWebviewWrapper; there is no legacy-bridge path. See the main plugin guide for enabling the New Architecture.

🚧

One WebView per screen

Web Integration attaches the bridge to a single WebView per screen. To render Taboola units into multiple WebViews on the same screen, use Classic integration.

Dependencies

Follow the main plugin installation steps, then add react-native-webview:

npm i react-native-webview

Initialize Taboola

Same as Classic — do this once, as early as possible (e.g. index.js / index.ts):

import { Taboola, TBLLogLevel } from '@taboola/react-native-plugin-4x';

Taboola.init('publisher-id');

// Optional but recommended while integrating — surfaces bridge-registration
// steps and render events in the native log.
Taboola.setLogLevel(TBLLogLevel.DEBUG);
📘

User consent (GDPR / CCPA)

The consent APIs (setUserConsent, setCCPA*, etc.) are shared with Classic — configure them in the same place. See GDPR and CCPA.

Create a Page instance

Create a Taboola Page for Web Integration once per screen, and remove it on unmount inside a useEffect cleanup:

const tblWebPage = useMemo(() => Taboola.getWebPage(), []);

useEffect(
  () => () => Taboola.removeWebPage(tblWebPage.pageId),
  [tblWebPage]
);
🚧

useMemo hook

When creating the TaboolaPage in a functional component, wrap the call in useMemo with a function initializer. Failure to do so can result in memory leaks.

📘

Page vs unit lifecycle

A page (Taboola.getWebPage()) outlives the unit built on it. TBLWebviewWrapper tears the unit down on its own unmount; the page's owner (this screen) must call Taboola.removeWebPage(pageId) when done. Teardown is order-independent and idempotent.

Create a Listener

Define a TBLWebListener to handle render events for the units placed inside your page:

const tblWebListener: TBLWebListener = {
  onRenderSuccessful(placement, height) { /* … */ },
  onRenderFailed(placement, error) { /* … */ },
};

See Callback surface for the full signatures.

Author the Taboola tag into your content page

Author the Taboola tag directly into the HTML of the page you load into the WebView. There are four building blocks — a viewport meta, the Mobile Loader script, one <div> per placement, and a config <script> at the bottom of <body>. Each is shown below on its own, followed by a compact end-to-end template.

1. Viewport meta

Add to <head>:

<meta name="viewport" content="width=device-width, user-scalable=no" />

2. Mobile Loader script

Add to <head>, replace publisher-id with your Taboola publisher ID, and set the page type on the first _taboola.push:

<script type="text/javascript">
  window._taboola = window._taboola || [];
  _taboola.push({ article: 'auto', url: '' });
  !function (e, f, u, i) {
    if (!document.getElementById(i)) {
      e.async = 1; e.src = u; e.id = i;
      f.parentNode.insertBefore(e, f);
    }
  }(document.createElement('script'),
    document.getElementsByTagName('script')[0],
    'https://cdn.taboola.com/libtrc/publisher-id/mobile-loader.js',
    'tb-mobile-loader-script');
</script>

Editing the first push:

  • The key (article in the snippet) is the page type. Replace with the type that matches your screen — one of article, video, photo, search, category, home (as provided by Taboola).
  • The value ('auto') leaves the ID generation to Taboola. To use your own internal ID instead, pass it as a string — e.g. { article: '123', url: '…' }.
  • url is a fully-qualified public URL that matches the content on the screen — e.g. 'https://www.example.com/articles?id=123'. Leave empty ('') only for pages that have no public URL.

3. Placement containers

For each placement, drop an empty <div> where you want Taboola content to render, and give it a unique id:

<div id="CONTAINER_ID"></div>

4. Placement + mobile config

At the bottom of <body>, push one placement config per container, then the mobile-specific config, then a single flush:

<script type="text/javascript">
  window._taboola = window._taboola || [];
  _taboola.push({
    mode: 'WIDGET_MODE',
    container: 'CONTAINER_ID',
    placement: 'PLACEMENT_NAME',
    target_type: 'mix'
  });

  window._taboola['mobile'] = window._taboola['mobile'] || [];
  _taboola['mobile'].push({
    lazyFetch: false,   // Mobile Loader fetches automatically on page load
    publisher: 'publisher-id'
  });

  _taboola.push({ flush: true });
</script>
📘

Placement values are Taboola-provided

CONTAINER_ID is a name you choose (must match the <div id> above). WIDGET_MODE, PLACEMENT_NAME, and publisher-id are values Taboola provides you for your account — don't invent them.

End-to-end template

Putting the four blocks together — one placement, ready to adapt (for multiple placements, add another <div> and another _taboola.push({ mode, container, placement, target_type }) before the mobile-config push):

<html>
  <head>
    <meta name="viewport" content="width=device-width, user-scalable=no" />
    <script type="text/javascript">
      // …Mobile Loader script from step 2…
    </script>
  </head>
  <body>
    <div id="CONTAINER_ID"></div>
    <!-- …your article content… -->
    <script type="text/javascript">
      // …Placement + mobile config from step 4…
    </script>
  </body>
</html>

A complete, runnable version (two placements + full IIFE) lives in the Sample App under example/src/utils/taboolaWebDemo.ts.

📘

Why author the tag into the HTML

Prefer authoring the Taboola tag into the page HTML, as the sample app does. Injecting the tag at runtime into a site you don't control can be blocked by that site's Content-Security-Policy — if the loader never loads, suspect CSP before a bridge bug.

Wrap the WebView

Wrap the publisher's <WebView> with <TBLWebviewWrapper>. On mount, the wrapper dispatches a Fabric command that resolves the WebView from its subtree and registers the Taboola bridge; on success, onWebviewRegistered fires with a TBLWebUnitController.

<TBLWebviewWrapper
  tblWebPage={tblWebPage}
  tblWebListener={tblWebListener}
  onWebviewRegistered={(controller) => {
    // Bridge is registered — now navigate to your real content page.
    setSource({ html: TABOOLA_CONTENT_HTML, baseUrl: CONTENT_BASE_URL });
  }}
  onWebviewRegistrationFailed={({ code, message }) => {
    // Terminal registration error (unknown page, native build failure).
  }}
>
  <WebView source={source} />
</TBLWebviewWrapper>

With lazyFetch: false in the web tag (recommended, as shown in the HTML above), the Mobile Loader fetches automatically once the content page loads — nothing else to call.

Deferred fetch (lazyFetch: true)

If you set lazyFetch: true in the web tag, content is not fetched until you explicitly request it via the controller:

onWebviewRegistered={(controller) => {
  // Later — e.g. when your ViewPager page becomes visible:
  controller.fetchContent();
}}
🚧

iOS race with early fetchContent()

Calling fetchContent() before the content page and Mobile Loader have finished loading loses the fetch and blocks later fetches for the unit's session (see MOB-6527). Only call fetchContent() after the content page has finished loading (e.g. gate on the WebView's onLoadEnd for the content URL). If you don't need deferred fetching, prefer lazyFetch: false and skip this call entirely.

Callback surface

CallbackWhereFires when
onWebviewRegistered(controller)<TBLWebviewWrapper>The bridge is registered on the WebView and the unit is ready. Receives a TBLWebUnitController.
onWebviewRegistrationFailed({ code, message })<TBLWebviewWrapper>Terminal registration error (unknown page, native build failure). See Registration never times out.
onRenderSuccessful(placement, height)TBLWebListenerA placement finished rendering inside the publisher's WebView. height is the rendered height in pixels.
onRenderFailed(placement, error)TBLWebListenerA placement failed to render. error is the reason reported by the SDK.

Other vendor callbacks (onItemClick, resize, reportAction, etc.) are not yet exposed through Web Integration.

Limitations & things to know

iOS: register before you load the real page

On iOS the bridge is only exposed to a page whose load starts after registration. Start the WebView on a blank page ({ html: '<html></html>' }) and navigate to your real content page inside onWebviewRegistered — as shown in Wrap the WebView.

iOS: WebView messaging is auto-enabled

The plugin enables react-native-webview's messagingEnabled for you during registration. Fallback: if on some react-native-webview version the bridge still doesn't appear on iOS, set a no-op onMessage={() => {}} on your WebView. Android is unaffected.

iOS: decelerationRate="normal" (optional)

Publishers who want the standard iOS scroll feel on long Taboola feeds can opt in on their own WebView:

<WebView decelerationRate="normal" ... />

Platform-guard the prop if your codebase is shared — under Fabric on Android, decelerationRate is typed as Double and passing the "normal" string crashes with ClassCastException.

Registration never times out

"No WebView yet" is a state, not an error — the plugin keeps waiting for a WebView that mounts late, lazily, or deep inside nested containers. onWebviewRegistrationFailed fires only for terminal errors (unknown page, native build failure).


Did this page help you?