SDK Reference
Full API reference for the Loopana SDKs.
Packages
| Package | Description |
|---|---|
@loopana/react | React provider and hooks |
@loopana/js | Browser client for any JS framework |
@loopana/node | Node.js server client |
@loopana/core | Types and pure utilities (isomorphic) |
@loopana/react
LoopanaProvider
Wrap your app to initialize the SDK and fetch all slot data in a single request.
import { LoopanaProvider } from "@loopana/react"
<LoopanaProvider
apiKey="pk_live_xxx"
userId={user.id}
locale="en"
>
{children}
</LoopanaProvider>
Props:
| Prop | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Project API key from Settings > API Keys |
userId | string | No | Stable user ID for targeting and A/B test selection |
locale | string | No | Locale code for translated content (e.g., fr, en-US) |
baseUrl | string | No | Override the API base URL |
eventsUrl | string | No | Override the events API base URL |
appOrigin | string | No | Your app's origin (e.g., https://myapp.com). Used to detect internal vs external URLs. |
dismissDurationDays | number | No | Default number of days a dismissed message stays hidden across all slots. Omit for an infinite dismiss. Override per slot via useSlot. |
onError | (err: unknown) => void | No | Called when a background operation (e.g., trackEvent) fails. |
The provider fetches all slots and messages for your project on mount. The data is then available via hooks.
useSlot
Hook to access messages for a specific slot.
import { useSlot } from "@loopana/react"
const { message, messages, dismiss, isLoading, error, trackEvent } = useSlot("homepage-banner")
Parameters:
| Parameter | Type | Description |
|---|---|---|
name | string | The slot name (must match the name in the dashboard) |
options | UseSlotOptions | Optional. Filter options. |
Options:
| Property | Type | Description |
|---|---|---|
event | string | Filter messages by event name |
dismissDurationDays | number | Days a dismissed message stays hidden for this slot before it can show again. Overrides the provider default. Omit to inherit it (or infinite if neither is set). |
Returns:
| Property | Type | Description |
|---|---|---|
message | SlotMessage | null | The current message to display. In Stack mode: first non-dismissed message. In A/B Test or Random mode: the selected message. null if no messages available or still loading. |
messages | SlotMessage[] | All messages for the slot (after mode filtering) |
isLoading | boolean | true during the initial fetch |
error | Error | null | Fetch error, if any |
dismiss | (messageId?: string) => void | Dismiss a message (Stack mode only). Defaults to the current message. Also records a dismiss analytics event. |
trackEvent | (type: EventType, messageId: string) => void | Manually track an event (impression, click, or dismiss) |
Filtering by event:
If you need different messages for different contexts within the same slot, pass an event filter:
const { message } = useSlot("notifications", { event: "low-token" })
Dismiss behavior
Calling dismiss() (Stack mode only) hides the message and records a dismiss event. Dismissals are stored in the browser's localStorage, so they are per device/browser — a different device, browser, or cleared storage will show the message again.
By default a dismiss is infinite: once dismissed, a message never reappears. To let a message show again after a delay, set dismissDurationDays:
// Re-show any dismissed message after 30 days, for every slot:
<LoopanaProvider apiKey="pk_live_xxx" dismissDurationDays={30}>
// Override for one slot (e.g. a promo you want back sooner):
useSlot("promo-banner", { dismissDurationDays: 7 })
Precedence: the per-slot useSlot value wins, otherwise the provider default, otherwise infinite. The duration is a developer setting — it is not controlled from the dashboard, so your app's UX stays in your control.
@loopana/js
Browser client for use with any JavaScript framework (Vue, Svelte, vanilla JS, etc.).
LoopanaClient
import { LoopanaClient } from "@loopana/js"
const client = new LoopanaClient({
apiKey: "pk_live_xxx",
appOrigin: "https://myapp.com", // optional
})
const messages = await client.fetchAll("en")
const banner = messages.get("homepage-banner")
Constructor options: Same as LoopanaProvider props (see above).
Methods:
| Method | Description |
|---|---|
fetchAll(locale?) | Fetch all slots and messages. Returns Map<string, SlotData>. |
getCache() | Returns the last fetched data, or null. |
getSlot(name) | Get a single slot from the cache. |
getSlotMessages(name, filter?) | Get filtered messages for a slot (excludes dismissed in Stack mode). |
trackEvent(payload) | Track an impression, click, or dismiss event. |
dismiss(messageId) | Dismiss a message and persist to localStorage. |
isDismissed(messageId, dismissDurationDays?) | Check if a message is currently dismissed (respecting the duration; infinite if omitted). |
setUserId(userId) | Update the user ID. |
getSessionId() | Get the current session ID. |
@loopana/node
Server-side client for Node.js, Next.js server components, API routes, etc. No browser APIs — works in any Node.js environment.
LoopanaClient
import { LoopanaClient } from "@loopana/node"
const client = new LoopanaClient({ apiKey: "pk_live_xxx" })
const messages = await client.fetchAll("en")
const banner = messages.get("homepage-banner")
if (banner) {
console.log(banner.messages[0].title)
}
Constructor options:
| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Project API key |
baseUrl | string | No | Override the API base URL |
eventsUrl | string | No | Override the events API base URL |
appOrigin | string | No | Your app's origin for URL resolution |
userId | string | No | End user ID for targeting |
onError | (err: unknown) => void | No | Called when trackEvent fails in the background. |
Methods:
| Method | Description |
|---|---|
fetchAll(locale?) | Fetch all slots and messages. Returns Map<string, SlotData>. |
trackEvent(payload) | Track an impression, click, or dismiss event. |
setUserId(userId) | Update the user ID. |
Types
SlotMessage
The message object returned by hooks and client methods.
| Property | Type | Description |
|---|---|---|
id | string | Unique message ID |
title | string | Message title (resolved for current locale) |
description | string | null | Optional description |
url | string | null | Optional link URL (internal URLs are resolved relative to appOrigin) |
actionLabel | string | null | Optional button text |
intent | string | Visual style: info, success, warning, destructive, or default |
event | string | null | Event name if the slot uses events |
priority | number | Sort priority (higher = first) |
isExternalUrl | boolean | Whether the URL points outside your app |
SlotData
| Property | Type | Description |
|---|---|---|
messages | SlotMessage[] | All messages in the slot |
events | string[] | Available event names |
mode | SlotMode | "stack", "ab_test", or "random" |
EventType
type EventType = "impression" | "click" | "dismiss"
Analytics
The SDK automatically tracks impressions when a message is rendered via useSlot. Click and dismiss events are tracked when the user interacts:
dismiss()fromuseSlotrecords a dismiss event- For clicks, call
trackEvent("click", messageId)
Analytics data appears in the dashboard under Analytics and on each message's detail page.