> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://contentful.com/developers/docs/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/_mcp/server. # Integrate the Optimization React Native SDK in a React Native app > This guide helps you add personalization, analytics, screen tracking, and a preview panel to a React Native (or Expo) application. ## Overview Use this guide to add Contentful personalization to a React Native or Expo app using `@contentful/optimization-react-native`. By the end of the quick start, one Contentful entry will render the personalized variant for the current visitor — or the original entry when none applies — and one screen event will report the visitor's screen view to Contentful, which uses it to keep that visitor's personalization consistent. **New to personalization?** Here is the whole idea in five points: * In Contentful you author **variants** of an entry and attach them to an **experience** — a rule that decides which visitors see which variant. * As the app runs, Contentful's **Experience API** looks at who the visitor is and picks the variant for each experience. Swapping a fetched entry for its picked variant is called **resolving** the entry. * The Experience API also returns a **profile**: the anonymous, per-visitor identity and state used to keep personalization consistent across requests or app launches. * Your app hands a Contentful entry to the SDK at the point where that entry becomes output. The SDK gives back the selected variant, or the original entry when no variant applies—the **baseline fallback**. You can fetch the entry yourself or give the SDK your Contentful client and an entry ID; either way, the client stays yours. * You render the returned entry with the same application components you already use. The React Native SDK persists the profile across app launches when persistence consent — a separate, durable consent that governs only whether the profile survives between launches — allows it. The "Consent and privacy-policy handoff" section below covers this and the other consent axis in full. That is enough to start. The guide introduces policy and optional capabilities at the point you need them. You will get there in two milestones: * **Milestone 1 — one entry resolving and one screen event (the quick start below).** A single screen mounts `OptimizedEntry`, which renders the resolved variant or the baseline, and reports one screen event. This is complete and shippable on its own. * **Milestone 2 — the opt-in layers (later).** Consent handoff, interaction tracking, identity, live updates, the preview panel, offline delivery, and analytics forwarding, each introduced by the section that needs it. This guide uses `@contentful/optimization-react-native`. You mount one `OptimizationRoot` around your app; it creates the SDK instance, restores state from AsyncStorage — React Native's community local- storage package, which your app must install alongside the SDK — and provides it to the hooks and components below it. Your app still owns its Contentful Delivery API (CDA) client, locale policy, consent policy, identity policy, navigation, and final rendering. ## Quick start Most React Native + Contentful apps share one shape: a screen fetches or receives a Contentful entry, and somewhere in that screen the entry becomes a rendered component. This quick start assumes that shape and proves the smallest result: **one screen renders a resolved entry — variant or baseline — and reports one screen event.** It mounts one `OptimizationRoot`, hands the SDK your Contentful client so it can fetch the entry by ID, and tracks the screen with `useScreenTracking`. This quick start assumes your application policy permits Optimization to start with accepted consent and renders no end-user consent UI, so it seeds `defaults={{ consent: true }}` — the shorthand that accepts both consent axes at once. If personalization must wait for a consent decision, keep this structure and add the [Consent and privacy-policy handoff](#consent-and-privacy-policy-handoff) step before you ship, which explains the two axes and the object form that sets them separately. 1. Install the React Native SDK, its required AsyncStorage peer dependency, and a Contentful delivery client if your app does not already have one. **Copy this:** ```sh pnpm add @contentful/optimization-react-native @react-native-async-storage/async-storage contentful ``` AsyncStorage ships native code, so complete your platform's native install step before launching: run `pod install` in `ios/` for a bare React Native app, or `npx expo prebuild` (a custom dev build) for Expo, then rebuild the app. 2. Mount `OptimizationRoot` with your app-owned Contentful client, emit one screen event for profile context, and render one single-locale Contentful entry by ID through `OptimizedEntry`. **Adapt this to your use case:** ```tsx import { Text } from 'react-native' import { OptimizationRoot, OptimizedEntry, useScreenTracking, } from '@contentful/optimization-react-native' import { createClient } from 'contentful' const APP_LOCALE = 'en-US' const contentfulClient = createClient({ accessToken: 'your-contentful-delivery-token', environment: 'master', space: 'your-space-id', }) function HomeScreen() { // Automatic screen tracking avoids sending duplicate screen events. useScreenTracking({ name: 'Home' }) // OptimizedEntry passes the selected variant or baseline fallback to the renderer. return ( {(resolvedEntry) => {`Resolved entry: ${resolvedEntry.sys.id}`}} ) } export function App() { // Use default accepted consent only when your application policy permits it. return ( ) } ``` The `App` and `HomeScreen` scaffolding above is illustrative context to match against your own app, not a file to paste over yours. Wrap your existing app root in `OptimizationRoot`, add `useScreenTracking` to a screen you already render, and wrap one entry you already render in `OptimizedEntry` — keep the rest of your components as they are. 3. Verify the first run. Launch the app; the screen displays a resolved entry ID for either the selected variant or the baseline. Because `logLevel="debug"` is set above, the SDK logs its activity to the console, so you can confirm the screen event fired by watching your Metro or device logs on mount for the `screen` event the SDK sends (the [Screen and navigation tracking](#screen-and-navigation-tracking) and [Analytics forwarding](#analytics-forwarding) sections add `states.eventStream` for a programmatic check). To see personalization rather than the baseline, author (in Contentful) a variant of the baseline entry whose ID you copied from Contentful, then attach it to an experience that targets all visitors — every visitor matches it automatically, so the resolved ID changes to the variant. (Authoring variants and experiences happens in the Contentful web app's Optimization experience builder, not in this SDK — this guide only covers consuming what you author there.) Without an authored variant every launch shows the baseline entry, which is expected, not a failure. ## Before you start The sections below walk the integration in order. First, gather the few things you can only get from outside this guide: * **A React Native or Expo app** with React and React Native installed, its own Contentful fetching already working, and the ability to run a native build step (`pod install` for bare React Native, `npx expo prebuild` for Expo) — the SDK's required AsyncStorage peer dependency ships native code. * **Contentful delivery credentials** — space ID, delivery token, and environment — read from your app's runtime configuration. * **At least one entry with a variant attached to an experience**, authored in Contentful. Without an authored variant, the integration can still run correctly while returning the baseline, so you cannot yet distinguish working personalization from a content-authoring gap. For the first personalized-content test, target all visitors so the test request or visitor matches automatically. * **Your Contentful space values** — space ID and environment, from your Contentful space settings. Find them in the Contentful web app under **Apps → Installed apps → Contentful Personalization → SDK keys**. The Experience API (which picks variants) and the Insights API (which receives event and interaction delivery) each have a base URL that defaults correctly; you only set them for mocks or non-default hosts (see [Install and initialize `OptimizationRoot`](#install-and-initialize-optimizationroot)). You do not need a setup inventory up front. Everything else — consent, entry resolution, screen tracking, interaction tracking, identity, live updates, preview, offline delivery — is introduced by the section that needs it. > **Info** > > Read the SDK and Contentful config from your app's runtime configuration. This guide's examples > use inline placeholder strings for clarity; the reference implementation reads `PUBLIC_...` > environment variables through `@env` because it runs against shared mock defaults. Use whatever > environment variable convention your React Native tooling already uses and keep it consistent. ## Core integration ### Install and initialize `OptimizationRoot` **Integration category:** Required for first integration You mounted `OptimizationRoot` in the quick start; this section covers its full configuration surface — `onStatesReady`, `api` host overrides, `logLevel`, and reading the SDK instance with `useOptimization()`. `OptimizationRoot` is the normal React Native entry point: it creates the SDK instance, waits for AsyncStorage-backed state setup, runs `onStatesReady` when provided, then renders provider children. It also composes live-update and interaction-tracking context for descendant components. 1. Mount one `OptimizationRoot` around all components that call React Native SDK hooks. 2. Pass `spaceId` from runtime configuration. Pass `environment` only when you do not use the default Contentful environment; when you omit it the SDK uses `master`. 3. Pass `locale` when Experience API responses and event context must use the same app locale as your Contentful entry fetches. 4. Pass `api` endpoint overrides only for staging, mocks, or non-default production hosts. Both base URLs default correctly otherwise, so most apps omit `api` entirely. 5. Provide `onStatesReady` when app-level code must subscribe to SDK state before child effects run (see [Analytics forwarding](#analytics-forwarding)). **Adapt this to your use case:** ```tsx import { OptimizationRoot } from '@contentful/optimization-react-native' import type { ReactNode } from 'react' export function AppRoot({ children }: { children: ReactNode }) { // Override API hosts only for staging, mocks, or non-default production hosts. return ( {children} ) } ``` Use `useOptimization()` under the provider when a component needs the SDK instance. The hook throws outside `OptimizationRoot` or `OptimizationProvider`, and the provider-owned path withholds children until the SDK is ready. Set `api.preflight = true` only for dry-run Experience API requests that aggregate a fresh profile state on the server without persisting it, for example when validating configuration or exercising targeting rules from a debug tool. It changes Experience delivery for the whole SDK instance, so leave it off in normal application builds. ### Consent and privacy-policy handoff **Integration category:** Common but policy-dependent Consent policy belongs to your application. Consent has two independent axes: `events` (may the SDK personalize and send events) and `persistence` (may the SDK store profile continuity in AsyncStorage). The shorthand `consent: true` sets both to `true`; the object form `{ events, persistence }` sets them separately. The SDK stores event consent, stores separate durable profile-continuity persistence consent, and blocks non-allowed event types until event consent is accepted. 1. If application policy permits Optimization by default and you do not render a user consent UI, seed accepted consent during SDK initialization. 2. If consent depends on user choice, leave `defaults.consent` unset and call `optimization.consent(true | false)` from the application-owned banner, CMP callback, or settings flow. 3. Use object-form consent when events are permitted but profile continuity must stay session-only. 4. Configure `allowedEventTypes` only after privacy review approves which events can emit before consent. 5. Subscribe to `states.blockedEventStream` during development when you need to verify blocked calls. **Adapt this to your use case:** ```tsx // Use this only when policy allows Optimization to start accepted. ``` **Adapt this to your use case:** ```tsx import { Button, View } from 'react-native' import { useOptimization } from '@contentful/optimization-react-native' function ConsentControls() { const optimization = useOptimization() return ( {/* Boolean consent updates event consent and durable profile-continuity consent together. */}