Skip to content

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.

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.

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>;

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 version
croctContent('home-banner', fallback);
// Fetch a specific version
croctContent('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.

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',
});

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:

OptionTypeDescription
timeoutnumberMaximum time in milliseconds to wait for a response. Setting this also disables localStorage persistence.
preferredLocalestring | ReadableAtom<string>The locale to use for content personalization (e.g., 'en-US'). Can be a Nanostores atom for automatic refresh on locale changes.
attributesobjectCustom 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',
});

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 root
croctContent('slot@1', fallback, {
attributes: atom({ currency: 'USD' }),
});
// Atom on a leaf value
croctContent('slot@1', fallback, {
attributes: { currency: atom('USD') },
});
// Atom nested deeper
croctContent('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.

Returns a CroctAtom — a read-only reactive atom that holds the current content state.

A read-only Nanostores atom with an additional refresh method.

type CroctAtom<
P extends JsonObject = JsonObject,
I extends VersionedSlotId = string,
> = ReadableAtom<State<I, P>> & {
refresh: () => Promise<void>;
};

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.

Type: State<I, P>

The current state of the atom. See State for the shape of this object.

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 complete
await bannerContent.refresh();
// Fire and forget
bannerContent.refresh();

If the refresh fails:

  • When the atom is in the loaded stage, it stays in loaded with the last successfully fetched content.
  • When the atom is in initial or fallback, it moves to fallback.

CroctAtom is a standard Nanostores ReadableAtom, so you can subscribe to it using any Nanostores-compatible method:

import { bannerContent } from '../stores/banner';
// Direct subscription
const unsubscribe = bannerContent.subscribe(state => {
console.log(state.stage, state.content);
});
// Framework hooks (React, Vue, Solid, Preact)
const state = useStore(bannerContent);
// Svelte
$bannerContent.content.title;

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.

function trackSessionField(path: string, atom: ReadableAtom<JsonValue>): UnbindFn;

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.

Returns an UnbindFn that stops the synchronization when called.

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 sync
stop();

Connects a Nanostores atom to a Croct user field. Updates are batched and sent to Croct through croct.user.edit().

function trackUserField(path: string, atom: ReadableAtom<JsonValue>): UnbindFn;

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.

Returns an UnbindFn that stops the synchronization when called.

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 sync
stop();

Connects a Nanostores atom to Croct cart tracking. When the atom updates, the latest cart snapshot is sent through the cartModified event.

function trackCart(atom: ReadableAtom<Cart | null>): UnbindFn;

Type: ReadableAtom<Cart | null>

The Nanostores atom containing the full current cart state. When the atom value is null, no event is tracked.

Returns an UnbindFn that stops the synchronization when called.

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 sync
stop();

The state object held by a CroctAtom. It is a tagged union discriminated by the stage field.

type State<I extends VersionedSlotId, P extends JsonObject> =
| { stage: 'initial' | 'fallback'; content: P; metadata?: never }
| { stage: 'loaded'; content: SlotContent<I, any>; metadata: SlotMetadata };

The atom’s starting state. Content is the fallback you provided. The first fetch is in progress or hasn’t started yet.

PropertyTypeDescription
stage'initial'Discriminant.
contentPThe fallback content.
metadataundefinedNot available in this stage.

A fetch was attempted but failed. Content is the fallback you provided.

PropertyTypeDescription
stage'fallback'Discriminant.
contentPThe fallback content.
metadataundefinedNot available in this stage.

Personalized content was successfully fetched from Croct.

PropertyTypeDescription
stage'loaded'Discriminant.
contentSlotContent<I>The personalized content from Croct, typed to the slot’s schema.
metadataSlotMetadataMetadata about the content delivery (experiment ID, experience ID, etc.).

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 stage
console.log(banner.content.title);