API reference
Croct Nanostores exports six members from the croct-nanostores package:
import { croct, croctContent, trackCart, trackSessionField, trackUserField, type CroctAtom,} from 'croct-nanostores';The Croct SDK singleton, re-exported from @croct/plug.
Importing croct from croct-nanostores registers the auto-refresh-atom plugin definition as a side effect. To enable auto-refresh, add 'auto-refresh-atom' to the plugins array when calling croct.plug():
import { croct } from 'croct-nanostores';
croct.plug({ appId: '<YOUR_APP_ID>', plugins: ['auto-refresh-atom'],});You can use this object for all standard Croct SDK operations — tracking events, managing user profiles, evaluating queries, and more. For the full API, see the Croct Plug SDK reference.
croctContent
Section titled “croctContent”Creates a reactive atom for a Croct slot. The atom fetches personalized content from Croct and can update automatically when user behavior changes if the auto-refresh-atom plugin is enabled.
Signature
Section titled “Signature”function croctContent<P extends JsonObject, const I extends VersionedSlotId>( slotId: I, fallbackContent: SlotContent<I, P>, options?: Omit<FetchOptions<SlotContent<I, P>>, 'fallback'>,): CroctAtom<P, I>;Parameters
Section titled “Parameters”slotId
Section titled “slotId”Type: VersionedSlotId
The identifier of the slot to fetch, optionally including a version. This follows the slotName or slotName@version format used throughout the Croct SDK.
// Fetch the latest versioncroctContent('home-banner', fallback);
// Fetch a specific versioncroctContent('home-banner@1', fallback);When you have type declarations generated by the Croct CLI, TypeScript validates that the slot ID matches a known slot and version.
fallbackContent
Section titled “fallbackContent”Type: SlotContent<I, P>
The content to use before the first successful fetch and whenever a fetch fails. This object must match the slot’s content schema.
The fallback serves as the initial value of the atom. Your components can render it immediately without waiting for a network request.
const banner = croctContent('home-banner@1', { title: 'Welcome', subtitle: 'Explore our latest collection', ctaLabel: 'Shop now', ctaLink: '/products',});options
Section titled “options”Type: Omit<FetchOptions<SlotContent<I, P>>, 'fallback'> (optional)
Additional options passed to the underlying croct.fetch() call. These are the same options supported by the Croct SDK’s fetch method, except for fallback (which is handled by the fallbackContent parameter instead).
Commonly used options include:
| Option | Type | Description |
|---|---|---|
timeout | number | Maximum time in milliseconds to wait for a response. Setting this also disables localStorage persistence. |
preferredLocale | string | ReadableAtom<string> | The locale to use for content personalization (e.g., 'en-US'). Can be a Nanostores atom for automatic refresh on locale changes. |
attributes | object | Custom attributes to pass to the personalization engine. Values at any nesting level can be Nanostores atoms — see reactive attributes. |
For the full list of supported options, see the croct.fetch() reference.
const banner = croctContent('home-banner@1', fallback, { timeout: 5000, preferredLocale: 'pt-BR',});Reactive attributes
Section titled “Reactive attributes”The attributes option supports Nanostores atoms at any level of the object tree. When any embedded atom updates, the content automatically refreshes with the resolved values:
import { atom } from 'nanostores';import { croctContent } from 'croct-nanostores';
const $currency = atom('USD');
const banner = croctContent('home-banner@1', fallback, { attributes: { currency: $currency },});
// Updating the atom triggers a content refresh$currency.set('BRL');You can also wrap the entire attributes object in an atom, or nest atoms arbitrarily deep — all of these are equivalent in behavior:
// Atom at the rootcroctContent('slot@1', fallback, { attributes: atom({ currency: 'USD' }),});
// Atom on a leaf valuecroctContent('slot@1', fallback, { attributes: { currency: atom('USD') },});
// Atom nested deepercroctContent('slot@1', fallback, { attributes: { user: { currency: atom('USD') } },});Passing a plain object with no atoms still works exactly as before. See reactive attributes for a detailed explanation of the behavior.
Return value
Section titled “Return value”Returns a CroctAtom — a read-only reactive atom that holds the current content state.
CroctAtom
Section titled “CroctAtom”A read-only Nanostores atom with an additional refresh method.
Type definition
Section titled “Type definition”type CroctAtom< P extends JsonObject = JsonObject, I extends VersionedSlotId = string,> = ReadableAtom<State<I, P>> & { refresh: () => Promise<void>;};Type parameters
Section titled “Type parameters”The type of the fallback content. This is inferred from the fallbackContent argument passed to croctContent.
The versioned slot ID. This is inferred from the slotId argument passed to croctContent.
Properties
Section titled “Properties”Type: State<I, P>
The current state of the atom. See State for the shape of this object.
refresh()
Section titled “refresh()”Type: () => Promise<void>
Triggers a fresh fetch from Croct and updates the atom with the result. The promise resolves when the fetch completes, regardless of whether it succeeded or failed.
// Wait for the refresh to completeawait bannerContent.refresh();
// Fire and forgetbannerContent.refresh();If the refresh fails:
- When the atom is in the
loadedstage, it stays inloadedwith the last successfully fetched content. - When the atom is in
initialorfallback, it moves tofallback.
Subscribing
Section titled “Subscribing”CroctAtom is a standard Nanostores ReadableAtom, so you can subscribe to it using any Nanostores-compatible method:
import { bannerContent } from '../stores/banner';
// Direct subscriptionconst unsubscribe = bannerContent.subscribe(state => { console.log(state.stage, state.content);});
// Framework hooks (React, Vue, Solid, Preact)const state = useStore(bannerContent);
// Svelte$bannerContent.content.title;trackSessionField
Section titled “trackSessionField”Connects a Nanostores atom to a Croct session field. When the atom updates, the new value is batched and sent to Croct through croct.session.edit(). Multiple tracked fields are collapsed into a single patch per tick.
Signature
Section titled “Signature”function trackSessionField(path: string, atom: ReadableAtom<JsonValue>): UnbindFn;Parameters
Section titled “Parameters”Type: string
The Croct session field path to update. Use dot notation for nested fields.
Type: ReadableAtom<JsonValue>
The Nanostores atom whose value should be pushed to the session field.
Return value
Section titled “Return value”Returns an UnbindFn that stops the synchronization when called.
Example
Section titled “Example”import { atom } from 'nanostores';import { trackSessionField } from 'croct-nanostores';
const $currency = atom('USD');const stop = trackSessionField('preferences.currency', $currency);
// Later, when you no longer want to syncstop();trackUserField
Section titled “trackUserField”Connects a Nanostores atom to a Croct user field. Updates are batched and sent to Croct through croct.user.edit().
Signature
Section titled “Signature”function trackUserField(path: string, atom: ReadableAtom<JsonValue>): UnbindFn;Parameters
Section titled “Parameters”Type: string
The Croct user field path to update. Use dot notation for nested fields.
Type: ReadableAtom<JsonValue>
The Nanostores atom whose value should be pushed to the user field.
Return value
Section titled “Return value”Returns an UnbindFn that stops the synchronization when called.
Example
Section titled “Example”import { atom } from 'nanostores';import { trackUserField } from 'croct-nanostores';
const $plan = atom('pro');const stop = trackUserField('account.plan', $plan);
// Later, when you no longer want to syncstop();trackCart
Section titled “trackCart”Connects a Nanostores atom to Croct cart tracking. When the atom updates, the latest cart snapshot is sent through the cartModified event.
Signature
Section titled “Signature”function trackCart(atom: ReadableAtom<Cart | null>): UnbindFn;Parameters
Section titled “Parameters”Type: ReadableAtom<Cart | null>
The Nanostores atom containing the full current cart state. When the atom value is null, no event is tracked.
Return value
Section titled “Return value”Returns an UnbindFn that stops the synchronization when called.
Example
Section titled “Example”import { atom } from 'nanostores';import { trackCart } from 'croct-nanostores';
const $cart = atom({ currency: 'USD', total: 99, items: [ { index: 0, quantity: 1, total: 99, product: { productId: 'sku-1', name: 'Starter pack', displayPrice: 99, }, }, ],});
const stop = trackCart($cart);
// Later, when you no longer want to syncstop();The state object held by a CroctAtom. It is a tagged union discriminated by the stage field.
Type definition
Section titled “Type definition”type State<I extends VersionedSlotId, P extends JsonObject> = | { stage: 'initial' | 'fallback'; content: P; metadata?: never } | { stage: 'loaded'; content: SlotContent<I, any>; metadata: SlotMetadata };Variants
Section titled “Variants”initial
Section titled “initial”The atom’s starting state. Content is the fallback you provided. The first fetch is in progress or hasn’t started yet.
| Property | Type | Description |
|---|---|---|
stage | 'initial' | Discriminant. |
content | P | The fallback content. |
metadata | undefined | Not available in this stage. |
fallback
Section titled “fallback”A fetch was attempted but failed. Content is the fallback you provided.
| Property | Type | Description |
|---|---|---|
stage | 'fallback' | Discriminant. |
content | P | The fallback content. |
metadata | undefined | Not available in this stage. |
loaded
Section titled “loaded”Personalized content was successfully fetched from Croct.
| Property | Type | Description |
|---|---|---|
stage | 'loaded' | Discriminant. |
content | SlotContent<I> | The personalized content from Croct, typed to the slot’s schema. |
metadata | SlotMetadata | Metadata about the content delivery (experiment ID, experience ID, etc.). |
Usage with type narrowing
Section titled “Usage with type narrowing”Because State is a discriminated union, TypeScript narrows the type when you check the stage:
const banner = useStore(bannerContent);
if (banner.stage === 'loaded') { // TypeScript knows `metadata` is available here console.log(banner.metadata.experimentId);}
// `content` is always available, regardless of stageconsole.log(banner.content.title);