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.pushconfig). - You want the auto-fetch flow (
lazyFetch: false) — the Mobile Loader fetches automatically once your content page loads.
New Architecture onlyRegistration is driven by a Fabric command (
registerWebView) onTBLWebviewWrapper; there is no legacy-bridge path. See the main plugin guide for enabling the New Architecture.
One WebView per screenWeb 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-webviewInitialize 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]
);
useMemohookWhen creating the Taboola
Pagein a functional component, wrap the call inuseMemowith a function initializer. Failure to do so can result in memory leaks.
Page vs unit lifecycleA page (
Taboola.getWebPage()) outlives the unit built on it.TBLWebviewWrappertears the unit down on its own unmount; the page's owner (this screen) must callTaboola.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 (
articlein the snippet) is the page type. Replace with the type that matches your screen — one ofarticle,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: '…' }. urlis 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_IDis a name you choose (must match the<div id>above).WIDGET_MODE,PLACEMENT_NAME, andpublisher-idare 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 HTMLPrefer 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>tblWebPage— thePageinstance from Create a Page instance.tblWebListener— the listener from Create a Listener.source— auseStateholding the current WebView source. Start it on a blank page ({ html: '<html></html>' }) and, insideonWebviewRegistered, set it to your real content page. See iOS: register before you load the real page.
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)
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 earlyfetchContent()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 callfetchContent()after the content page has finished loading (e.g. gate on the WebView'sonLoadEndfor the content URL). If you don't need deferred fetching, preferlazyFetch: falseand skip this call entirely.
Callback surface
| Callback | Where | Fires 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) | TBLWebListener | A placement finished rendering inside the publisher's WebView. height is the rendered height in pixels. |
onRenderFailed(placement, error) | TBLWebListener | A 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)
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).
Updated 1 day ago
