npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@salesforce-personalization/react-native-personalization

v1.0.0

Published

A React Native bridge library that enables seamless integration between the Personalization native iOS/Android SDK and React Native applications.This library exposes the native SDK’s features and APIs to JavaScript, allowing React Native client apps to ac

Downloads

24

Readme

Salesforce Personalization SDK for React Native

A React Native plugin that wraps the native iOS / Android Salesforce Personalization SDKs. Renders personalized UI (Banner, Recommendations) in React Native apps and lets you ship your own custom components against the same backend.

Looking for a runnable demo? See example/.

Table of Contents

Requirements

| Requirement | Version | | ------------ | ------------- | | React Native | 0.76+ | | React | 18.3+ or 19.x | | Node.js | 18+ | | iOS | 15.0+ | | Android | API 26+ |

Install

npm install @salesforce-personalization/react-native-personalization
# or
yarn add @salesforce-personalization/react-native-personalization

Platform Setup

The host app owns native SDK initialization — there is no JS-side configure(...). The SDK needs three values: CDP_APP_ID, CDP_ENDPOINT, and CDN_URL. Contact your Salesforce Marketing Cloud administrator to obtain them, and don't commit real credentials.

After installing, follow the platform setup guide:

  • iOS — see docs/iOS.md for Info.plist, CocoaPods, and AppDelegate.swift setup.
  • Android — see docs/Android.md for AndroidManifest.xml and MainApplication.kt setup.

Quick Start

Once the SDK is initialized natively, render personalized content with ContentZone. It fetches content for the named zone, matches the backend's component name against your allowedComponents, and renders the result.

import { ContentZone, Banner, Recommendations } from '@salesforce-personalization/react-native-personalization'

function HomeScreen() {
  return (
    <ContentZone
      name="HomeScreen"
      allowedComponents={[
        Banner({ onTap: (event) => console.log('Banner tapped:', event.header) }),
        Recommendations({ onTap: (event) => console.log('Item tapped:', event.item.name) }),
      ]}
      loading={<Text>Loading personalized content...</Text>}
      fallback={(error) => <Text>Could not load content</Text>}
    />
  )
}

Only name and allowedComponents are required. If the backend returns a component name not in allowedComponents — or anything else goes wrong — the optional fallback is rendered. When fallback is omitted the zone renders nothing.

See docs/GETTING_STARTED.md for the full guide.

Core APIs

ContentZone

The main component for displaying personalized content.

| Prop | Type | Required | Description | | ------------------------- | -------------------------------- | -------- | --------------------------------------------- | | name | string | Yes | Unique zone identifier matching server config | | allowedComponents | Component[] | Yes | Component renderers the zone may use | | controller | ContentZoneController | No | Controller for programmatic refresh | | timeoutSeconds | number | No | Fetch timeout in seconds (default: 10) | | decisionsRequestContext | DecisionsRequestContext | No | Contextual data to bias personalization | | loading | ReactNode | No | Loading-state UI | | fallback | (error: Error) => ReactNode | No | Error-state UI |

Pull-to-refresh. Pass a controller from useContentZoneController and route a RefreshControl to controller.refresh():

const controller = useContentZoneController()

<ScrollView
  refreshControl={<RefreshControl refreshing={refreshing} onRefresh={() => controller.refresh()} />}
>
  <ContentZone controller={controller} name="HomeScreen" allowedComponents={[Banner()]} />
</ScrollView>

controller.refresh() keeps existing content visible during the fetch; call controller.refresh(true) to show the loading state instead.

Preview Mode

Forward preview deep links to the SDK so active zones refresh with preview content:

import { Linking } from 'react-native'

useEffect(() => {
  const handleUrl = ({ url }: { url: string }) => {
    if (url.includes('sfp-preview')) PersonalizationModule.handlePreviewUrl(url)
  }
  Linking.getInitialURL().then((url) => url && handleUrl({ url }))
  const sub = Linking.addEventListener('url', handleUrl)
  return () => sub.remove()
}, [])

Deep-link setup is covered in docs/iOS.md and docs/Android.md.

Out-of-the-Box Components

Two ready-to-use components render production content with automatic View/Click engagement tracking. Both accept an optional onTap callback and a style config.

Banner

A single banner with image, title, subtitle, and CTA. Backed by the Salesforce_Banner transformer.

Banner({
  onTap: (event) => console.log('Banner tapped:', event.header),
  style: {
    backgroundColor: '#f0f0f0',
    headerTextColor: '#1a1a1a',
    ctaTextColor: '#0066cc',
  },
})

The tap event is the full BannerModel: { id?, header, subheader?, imageUrl, ctaText?, ctaUrl? }.

Recommendations

A scrollable list of recommendation cards. Backed by the Salesforce_Recommendations transformer.

Recommendations({
  onTap: (event) => console.log('Item tapped:', event.item.name, 'at index:', event.index),
  style: {
    cardSpacing: 16,
    card: { nameTextColor: '#1a1a1a', descriptionTextColor: '#666' },
  },
})

The model is shared ctaText plus a list of items:

type RecommendationItem = {
  id: string
  name: string
  description?: string
  imageUrl: string
  url?: string
}

interface RecommendationsModel {
  sectionHeader?: string
  ctaText?: string          // shared by all items
  items: RecommendationItem[]
}

The tap event is { item: RecommendationItem, index: number }. See docs/GETTING_STARTED.md for the full style reference.

Engagement Tracking

The OOTB Banner and Recommendations components track View and Click automatically — no work required. Tracking runs for both PRODUCTION and PREVIEW content; MOCK content (via MockContentZone) is never tracked because it carries no engagement payloads.

Custom components opt in via the engagementPayloads the SDK delivers on ComponentContext. The shape depends on the transformer:

  • Salesforce_Banner (single element) → PerAction. Look payloads up with getPayloadForAction(payloads, action).
  • Salesforce_Recommendations (list) → PerItemAndAction. Look payloads up per card with getPayloadForItemAndAction(payloads, index, action).

Both helpers and trackEngagement are imported from the package root:

import { getPayloadForAction, trackEngagement } from '@salesforce-personalization/react-native-personalization'

// Inside a custom component's compose():
useEffect(() => {
  if (!context.engagementPayloads) return
  const payload = getPayloadForAction(context.engagementPayloads, 'View')
  if (payload) trackEngagement(payload)
}, [context.personalizationId]) // re-fires on refresh — new content, new View

Action names ('View', 'Click', …) are server-defined and matched case-insensitively. Key View tracking on personalizationId so a refresh fires a fresh View. See the Custom Components Guide for a full worked example.

Identity

import { PersonalizationModule } from '@salesforce-personalization/react-native-personalization'

// Profile ID (contact key)
await PersonalizationModule.setProfileId('user-123')

// Attributes — all values must be strings
await PersonalizationModule.setAttribute('tier', 'gold')
await PersonalizationModule.setAttributes({ firstName: 'John', lastName: 'Doe' })

// Read
const profileId = await PersonalizationModule.getProfileId()
const attributes = await PersonalizationModule.getAttributes()

// Clear (typical logout path)
await PersonalizationModule.clearAllAttributes()

// Party identification — per-field setters / getters
await PersonalizationModule.setPartyIdentificationName('John Doe')
await PersonalizationModule.setPartyIdentificationNumber('12345')
await PersonalizationModule.setPartyIdentificationType('individual')

Events

All tracking flows through PersonalizationModule.track(event). Pick the objType that matches the customer journey:

// Custom — free-form name + attributes
await PersonalizationModule.track({
  objType: 'CustomEvent',
  name: 'product_viewed',
  attributes: { productId: 'ABC123', category: 'shoes' },
})

// Cart — subtype: 'add' | 'remove' use a singular `lineItem`;
//        subtype: 'replace' uses a `lineItems` array
await PersonalizationModule.track({
  objType: 'CartEvent',
  subtype: 'add',
  lineItem: { catalogObjectType: 'product', catalogObjectId: 'ABC123', quantity: 2, price: 49.99, currency: 'USD' },
})

// Order — subtype: 'purchase' | 'preorder' | 'cancel' | 'ship' | 'deliver' | 'return' | 'exchange'
await PersonalizationModule.track({
  objType: 'OrderEvent',
  subtype: 'purchase',
  order: { id: 'ORDER-123', lineItems: [/* ... */], totalValue: 99.99, currency: 'USD' },
})

// Catalog — subtype: 'view' | 'viewDetail' | 'quickView' | 'favorite' | 'share' | 'review' | 'comment'
await PersonalizationModule.track({
  objType: 'CatalogEvent',
  subtype: 'view',
  catalogObject: { type: 'product', id: 'ABC123', attributes: { name: 'Blue Sneakers' } },
})

See docs/GETTING_STARTED.md for the full event reference.

Consent

Personalization requires consent. Until the user is opted in, the SDK does not fetch personalized content.

await PersonalizationModule.setConsent(true)   // OPT_IN
await PersonalizationModule.setConsent(false)  // OPT_OUT
const isOptedIn = await PersonalizationModule.isConsentOptIn()

Custom Components

Render personalized content however you want by implementing Component<T>. Two patterns:

  1. Reuse an OOTB transformer (e.g. Salesforce_Banner, Salesforce_Recommendations) with your own UI — you keep automatic engagement tracking and need no backend configuration.
  2. Define a fully custom transformer with your own fields — you implement engagement tracking manually.
import {
  Component, ComponentModel, ComponentContext,
  getPayloadForAction, trackEngagement,
} from '@salesforce-personalization/react-native-personalization'

interface ProductCardModel extends ComponentModel {
  header: string
  imageUrl: string
  ctaText?: string
}

export function ProductCard(config?: { onTap?: (m: ProductCardModel) => void }): Component<ProductCardModel> {
  return {
    name: 'Salesforce_Banner', // reuse the Banner transformer's data + engagement payloads
    validateAndCreateComponentModel: (json, context) => {
      const data = JSON.parse(json)
      if (!data.header || !data.imageUrl) throw new Error('Missing required fields')
      return data as ProductCardModel
    },
    compose: (model, context) => {
      useEffect(() => {
        if (context.engagementPayloads) {
          const payload = getPayloadForAction(context.engagementPayloads, 'View')
          if (payload) trackEngagement(payload)
        }
      }, [context.personalizationId])

      return (
        <TouchableOpacity onPress={() => config?.onTap?.(model)}>
          <Image source={{ uri: model.imageUrl }} style={{ width: '100%', height: 200 }} />
          <Text>{model.header}</Text>
        </TouchableOpacity>
      )
    },
  }
}

ComponentContext carries:

  • contentSource: 'PRODUCTION' | 'PREVIEW' | 'MOCK'
  • personalizationId: unique per fetch (changes on refresh — key your View tracking on it)
  • engagementPayloads: automatic tracking data (OOTB transformers only)

📖 See the Custom Components Guide for complete examples with engagement tracking, including Recommendations-shaped payloads (getPayloadForItemAndAction).

Architecture Compatibility

This plugin uses TurboModules but works in both Legacy (Bridge) and New Architecture apps on React Native 0.76+. Your JS/TS code needs no changes either way — the plugin ships a codegenConfig in package.json, and React Native's autolinking generates the native TurboModule specs for you on both architectures. You don't run Codegen manually or add any Gradle/CocoaPods wiring yourself.

To run your app on the New Architecture:

  • Android — set in android/gradle.properties:

    newArchEnabled=true

    then do a clean rebuild: cd android && ./gradlew clean.

  • iOS — set the flag before installing pods:

    cd ios && RCT_NEW_ARCH_ENABLED=1 pod install

To stay on the Legacy Architecture, leave newArchEnabled=false (Android's default in the RN template) and run a plain pod install (no RCT_NEW_ARCH_ENABLED) — the plugin runs through React Native's interop layer with no code changes required.

Either way, PersonalizationModule, ContentZone, and every other exported API behave identically. See TROUBLESHOOTING.md if you hit a Codegen or CMake linking error after switching architectures.

More

License

BSD-3-Clause License — see LICENSE for details.

Support