The Optimization SDK Suite is pre-release (alpha). Breaking changes can be published at any time.
The Optimization React Native SDK provides a stateful mobile runtime on top of the
Optimization Core SDK. It adds React providers, hooks,
OptimizedEntry, screen tracking, optional offline-aware event delivery, and in-app preview-panel
support for React Native applications.
If you are integrating a React Native application, start with Getting Started, then use Integrating the Optimization React Native SDK in a React Native app for the step-by-step flow. This README keeps the package orientation and common setup options close at hand; generated reference documentation remains the source of truth for exported API signatures.
Install using an NPM-compatible package manager, pnpm for example:
pnpm install @contentful/optimization-react-native @react-native-async-storage/async-storage
For offline support, also install NetInfo:
pnpm install @react-native-community/netinfo
Wrap the app with OptimizationRoot near the top of the component tree:
import { OptimizationRoot } from '@contentful/optimization-react-native'
export default function App() {
return (
<OptimizationRoot
clientId="your-client-id"
environment="main"
contentfulLocales={{
default: 'en-US',
}}
>
<YourApp />
</OptimizationRoot>
)
}
For non-component ownership paths, create the SDK instance explicitly and call methods directly:
import { ContentfulOptimization } from '@contentful/optimization-react-native'
const optimization = await ContentfulOptimization.create({
clientId: 'your-client-id',
environment: 'main',
contentfulLocales: {
default: 'en-US',
},
})
Use @contentful/optimization-react-native for React Native applications that need mobile
optimization, persisted state, screen tracking, entry interaction tracking, offline behavior, and
preview-panel support. Native iOS and Android package work also exists in this monorepo for teams
that need platform-native integration surfaces.
OptimizationRoot accepts Core stateful configuration directly, plus React Native-specific props.
Only clientId is required.
| Option | Required? | Default | Description |
|---|---|---|---|
clientId |
Yes | N/A | Shared API key for Experience API and Insights API requests |
environment |
No | 'main' |
Contentful environment identifier |
api |
No | See API options below | Experience API and Insights API endpoint and request options |
contentfulLocales |
No | undefined |
Contentful locale codes used for SDK-assisted CDA locale resolution |
locale |
No | undefined unless contentfulLocales is set |
Initial app/content locale candidate; changing this prop updates the owned SDK locale |
defaults |
No | undefined |
Initial state, commonly including consent, persistence consent, or profile values |
allowedEventTypes |
No | ['identify', 'screen'] |
Event types allowed before consent is explicitly set |
trackEntryInteraction |
No | { views: true, taps: false } |
Default view and tap tracking for OptimizedEntry components |
liveUpdates |
No | false |
Whether optimized entries react continuously to SDK state changes |
previewPanel |
No | undefined |
Enables the in-app preview panel when provided with enabled: true |
onStatesReady |
No | undefined |
Provider-managed app-level state subscription hook |
getAnonymousId |
No | undefined |
Function used to provide an anonymous ID from application-owned identity state |
queuePolicy |
No | SDK defaults | Flush retry behavior and offline queue bounds |
logLevel |
No | 'error' |
Minimum log level for the default console sink |
onEventBlocked |
No | undefined |
Callback invoked when consent or guard logic blocks an event |
Common api options:
| Option | Required? | Default | Description |
|---|---|---|---|
experienceBaseUrl |
No | 'https://experience.ninetailed.co/' |
Base URL for the Experience API |
insightsBaseUrl |
No | 'https://ingest.insights.ninetailed.co/' |
Base URL for the Insights API |
locale |
No | API default | Locale query parameter for localized Experience API responses |
enabledFeatures |
No | ['ip-enrichment', 'location'] |
Experience API features to apply to each request |
preflight |
No | false |
Aggregate a new profile state without storing it |
beaconHandler |
No | undefined |
Custom handler for enqueueing Insights API batches when needed |
Common fetchOptions are fetchMethod, requestTimeout, retries, intervalTimeout,
onFailedAttempt, and onRequestTimeout. Default retries intentionally apply only to HTTP 503
responses.
Use contentfulLocales.default for single-locale apps. For apps that match device locale input to
multiple Contentful locales, keep default as the fallback and list the supported Contentful locale
codes:
<OptimizationRoot
clientId="your-client-id"
contentfulLocales={{
default: 'en-US',
supported: ['en-US', 'de-DE', 'fr-FR'],
}}
>
<YourApp />
</OptimizationRoot>
Copy contentfulLocales.default and optional contentfulLocales.supported from Contentful locale
settings or the CMA locale list. The resolved optimization.locale, when present, is the configured
Contentful locale code to use for CDA fetches and default Experience API localization. api.locale
remains an explicit Experience API override, and withOptimizationLocale(contentfulClient) reads
the live SDK locale and injects it into getEntry() and getEntries() calls when a caller does not
provide one. See
Locale handling in the Optimization SDK Suite
for the full locale model.
For provider-owned SDK instances, changing the locale prop calls sdk.setLocale() after
initialization while the rest of the SDK config remains initialization-scoped. Locale updates do not
fetch content or refresh profile state; trigger your app's normal screen(), identify(), or CDA
fetch flow when localized data needs to change.
For every prop, callback payload, and exported type, use the generated React Native SDK reference.
OptimizationRoot owns async React Native SDK initialization. It renders no children while platform
state setup is pending, runs any onStatesReady callback, and only then renders provider children.
This matches the React Web provider contract, but React Native uses async effect scheduling because
storage and platform setup cannot be completed before paint.
When persistence consent permits durable profile continuity, SDK state from an Experience response
is published only after the corresponding AsyncStorage write for that same response snapshot has
settled or failed gracefully. AsyncStorage hydrates continuity during initialization and mirrors
changes for the next launch; after startup, sdk.states, entry rendering, and tracking metadata
read from in-memory SDK state. Application code can wait for sdk.states.profile,
sdk.states.selectedOptimizations, or rendered SDK-derived UI instead of adding storage-timing
delays before a relaunch-sensitive action.
Consent policy remains application-owned. For default-on application policies that do not render an
end-user consent prompt, set defaults: { consent: true } on OptimizationRoot:
<OptimizationRoot clientId="your-client-id" defaults={{ consent: true }}>
<YourApp />
</OptimizationRoot>
When application policy depends on user choice, leave defaults.consent unset and call
consent(true | false) from the application-owned control. Boolean consent calls control both event
emission and durable profile-continuity persistence by default. Use
consent({ events: true, persistence: false }) when events are allowed but continuity should stay
session-only.
Do not use a component effect to grant default consent for an app that has no consent prompt.
Seeding defaults.consent during SDK initialization applies the persistence policy before child
effects, screen tracking, or manual identify() calls can run.
AsyncStorage persists consent and, when persistence consent permits it, profile-continuity state across app launches. It is not consulted for every live state read after initialization. For cross-SDK consent guidance, see Consent management in the Optimization SDK Suite.
OptimizedEntry resolves optimized Contentful entries and passes non-optimized entries through
unchanged:
import { OptimizedEntry } from '@contentful/optimization-react-native'
function HeroEntry({ entry }) {
return (
<OptimizedEntry baselineEntry={entry}>
{(resolvedEntry) => <Hero data={resolvedEntry.fields} />}
</OptimizedEntry>
)
}
Fetch Contentful entries in your app layer. For optimized entries, request linked entries deeply
enough for the baseline and variants, commonly with include: 10.
Use one CDA locale for entries passed to OptimizedEntry or useEntryResolver(). For localized
apps, configure contentfulLocales, then use the recommended
optimization.withOptimizationLocale(contentfulClient) helper or pass optimization.locale
explicitly so entry fetches use the SDK-resolved locale. Without contentfulLocales or an explicit
top-level locale, the wrapper omits the CDA locale and lets Contentful use the space default. Do
not pass all-locale CDA responses from withAllLocales or locale=*; these APIs expect direct
single-locale field values. See
Entry personalization and variant resolution
for the entry contract and
Locale handling in the Optimization SDK Suite
for runtime locale behavior.
Use useEntryResolver() when a component needs manual entry resolution without the OptimizedEntry
wrapper:
import { useEntryResolver } from '@contentful/optimization-react-native'
function HeroData({ entry }) {
const { resolveEntry } = useEntryResolver()
const resolvedEntry = resolveEntry(entry)
return <Hero data={resolvedEntry.fields} />
}
Entry tracking records views and taps for Contentful entries, not arbitrary UI components. Global
defaults live on OptimizationRoot; individual OptimizedEntry components can override them:
<OptimizationRoot clientId="your-client-id" trackEntryInteraction={{ views: true, taps: true }}>
<OptimizedEntry baselineEntry={entry} trackViews={true} trackTaps={false}>
{(resolvedEntry) => <Card entry={resolvedEntry} />}
</OptimizedEntry>
</OptimizationRoot>
Wrap scrollable screens with OptimizationScrollProvider so view tracking uses actual scroll
position instead of only screen dimensions.
Use OptimizationNavigationContainer to emit screen events from React Navigation changes:
<OptimizationNavigationContainer>
{(navigationProps) => (
<NavigationContainer {...navigationProps}>{/* navigators */}</NavigationContainer>
)}
</OptimizationNavigationContainer>
Use useScreenTracking() for screen-level control and useScreenTrackingCallback() when names are
derived from navigation state or other dynamic data.
Use onStatesReady when application code needs SDK state subscriptions that line up with provider
initialization. The provider calls it after async SDK state setup completes and before child screen,
navigation, or entry effects can emit events.
<OptimizationRoot
clientId="your-client-id"
onStatesReady={(states) => {
const subscriptions = [
states.eventStream.subscribe((event) => {
if (event) devToolsPanel.logEvent(event)
}),
states.blockedEventStream.subscribe((blocked) => {
if (blocked) devToolsPanel.logBlockedEvent(blocked)
}),
]
return () => {
subscriptions.forEach((subscription) => subscription.unsubscribe())
}
}}
>
<YourApp />
</OptimizationRoot>
The callback receives only sdk.states. Use regular hooks and React effects for component-local UI
state under the provider.
Use OptimizationProvider directly with a pre-built sdk only when an application or framework
adapter owns initialization. Without onStatesReady, children render immediately because the SDK is
already available. When onStatesReady is provided, the provider waits until those subscribers are
attached before children mount and runs the returned cleanup on unmount. In both cases, it does not
call destroy() on the injected SDK.
liveUpdates controls whether OptimizedEntry continuously reacts to SDK state changes. The
preview panel always forces live updates on while it is open.
<OptimizationRoot clientId="your-client-id" liveUpdates={true}>
<OptimizedEntry baselineEntry={entry} liveUpdates={false}>
{(resolvedEntry) => <Card entry={resolvedEntry} />}
</OptimizedEntry>
</OptimizationRoot>
Enable the preview panel only in authoring or development flows and provide a Contentful client:
<OptimizationRoot clientId="your-client-id" previewPanel={{ enabled: __DEV__, contentfulClient }}>
<YourApp />
</OptimizationRoot>
When NetInfo is installed, the SDK can queue events while the device is offline and flush them after
connectivity returns. When the app moves to the background or inactive state, the SDK also flushes
queued events and drains pending AsyncStorage persistence. Tune queue bounds and retry behavior with
queuePolicy when the defaults are not appropriate for your app.
ContentfulOptimization.create(...) is asynchronous. Prefer OptimizationRoot when React needs
to own initialization.crypto.randomUUID(), and crypto.getRandomValues(); applications do not need additional setup
beyond installing this SDK and its documented dependencies.destroy() before creating replacement instances in tests or hot-reload workflows.