Skip to main content

SDK Helper

NubeSDK apps run inside an isolated web worker with no direct access to the DOM. Everything your app does: reading state, rendering components, navigating, and storing data goes through the SDK instance the runtime hands to your App(nube) entry point.

In practice, that nube instance ends up threaded through every function and component, and a handful of patterns get re-implemented in every project: reading the current page, narrowing page types, rendering one component per product, showing a toast when an event fires.

@tiendanube/nube-sdk-helper packs those patterns into a small, strongly-typed toolkit.

Installation

npm install @tiendanube/nube-sdk-helper @tiendanube/nube-sdk-types

@tiendanube/nube-sdk-types is a peer dependency and must be installed alongside the helper package.

Registering the instance

The runtime passes the SDK instance only as the argument of your entry point. The helper's core idea is simple: register it once, and every other helper can reach it on its own, with no more passing nube down through your whole app.

src/App.ts
import {
setNubeInstance,
getCurrentState,
ui,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before anything else

const state = getCurrentState();
ui.showToast(`You are on the ${state.location.page.type} page`);
}

Three functions manage the instance:

  • setNubeInstance(nube): registers the instance (call once at the top of App).
  • getNubeInstance(): returns the registered instance, throwing a descriptive error if it has not been registered yet.
  • clearNubeInstance(): clears the instance (handy in tests).

Why this matters

With the instance registered globally, the most common actions become free-standing functions you can call from anywhere: a deeply nested component, a utility module, an event handler, without receiving nube as a parameter.

import { navigate, setNubeInstance, ui } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper
}

// Anywhere else in your app, with no `nube` in scope:
function onCheckoutClick() {
navigate("/checkout"); // routes to the path internally via the SDK instance
ui.showToast("Taking you to checkout...", "info");
}

Without the helper, you would need a nube reference in scope, call nube.getBrowserAPIs().navigate(...), and build the toast component by hand. The helper collapses both into one-liners.

The same applies to browser storage:

import { browser, setNubeInstance } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export async function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

await browser.asyncLocalStorage.setItem("seen-banner", "true");
const seen = await browser.asyncLocalStorage.getItem("seen-banner");
}

Reading state with selectors

Selectors follow one consistent pattern: call them with no argument and they read the current SDK state; pass an explicit state and they become pure functions.

import {
getCartItems,
getCustomer,
getPageType,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

// No argument: reads the current state from the SDK instance.
const items = getCartItems();
const pageType = getPageType();
const customer = getCustomer();
}
Testability

Pass a mock state to any selector and it behaves as a pure function: no side effects, no dependency on the registered instance. This pattern applies to every selector in the family.

Guards

Guards do double duty: they validate at runtime and narrow the type for TypeScript, unlocking the page-specific typed data for the compiler.

import {
getCurrentState,
isProductPage,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

const { page } = getCurrentState().location;

if (isProductPage(page)) {
// `page` is now narrowed to a ProductPage, so `page.data.product` is typed.
console.log(page.data.product.name);
}
}
Validation and type narrowing together

Unlike a cast (as ProductPage), guards check the structure at runtime before narrowing the type. If the condition does not pass, TypeScript does not expose page.data.product.

There is a guard for nearly every shape you will encounter in a NubeSDK app:

  • Pages: isProductPage, isCategoryPage, isCheckoutPage, isHomePage, isAllProductsPage, isSearchPage
  • Cart: isCart, isCartItem, isCartValidationSuccess, isCartValidationPending, isCartValidationFail
  • Domain: isStore, isCustomer, isPayment, isShipping, isAddress, and more
  • Components / page data: isNubeComponent, hasProductList, hasSections, hasSingleProduct, isSectionWithProducts

Getters

Beyond state selectors, getters expose the metadata the runtime injects about your app. A useful one is getScriptURL, which parses the URL your app script was loaded from (cached as a frozen URL):

import {
getScriptParam,
getScriptURL,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

const url = getScriptURL();
console.log("Script origin:", url.origin);
console.log("Script pathname:", url.pathname);

// Read configuration passed as query params on the script URL, e.g. ?variant=b
const variant = getScriptParam("variant"); // string | null
}

This is the idiomatic way to configure an app from the script tag without shipping a separate config request.

Page matching

pageMatch and onPage solve the same problem from two angles. pageMatch dispatches once against a state you provide, and each handler receives the correctly-typed payload for its page. onPage wraps pageMatch but subscribes to navigation, re-running on every page change and returning an unsubscribe function.

Use pageMatch for a one-off decision based on the current state:

import {
getCurrentState,
pageMatch,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

// Runs once against the state you pass in.
pageMatch(getCurrentState(), {
product: (state, product) => console.log("Product:", product.name),
checkout: (state, checkout) => console.log("Step:", checkout.step),
});
}

Use onPage to keep reacting as the user navigates:

import { onPage, setNubeInstance } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

const stop = onPage({
product: (state, product) => console.log("Viewing product:", product.id),
checkout: (state, checkout) => {
if (checkout.step === "success") console.log("Purchase complete");
},
// category / home handlers are optional
});

// Call stop() later, when you no longer need to react to navigation.
void stop;
}

For checkout specifically, onCheckoutStep lets you react to a particular step:

import { onCheckoutStep, setNubeInstance } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

onCheckoutStep({
success: () => console.log("Purchase complete"),
});
}

Render

A frequent need is rendering something into a per-product grid slot: a badge, a label, an icon. forEachProduct extracts every product from the current state (regardless of page type), maps each one through a render factory, drops empty results, and auto-assigns a unique key from the product id.

import {
forEachProduct,
onPage,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
import { Badge } from "./components/Badge";

export function App(nube: NubeSDK) {
setNubeInstance(nube);

// Render a badge over every product image in the home grid.
onPage({
home: () => {
nube.render(
"product_grid_item_image_center_center",
forEachProduct((product) => <Badge product={product} />),
);
},
});
}

Without JSX, the factory returns a component object directly:

import {
forEachProduct,
onPage,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

onPage({
home: () => {
nube.render(
"product_grid_item_image_bottom_right",
forEachProduct((product) => ({ type: "txt", children: product.name })),
);
},
});
}

Return null or undefined from the factory to skip a product: those entries are filtered out automatically.

UI helpers and events

ui wraps the most common view operations, including rendering the same component across multiple slots in one call:

import { setNubeInstance, ui } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

ui.renderAll(["corner_top_left", "corner_top_right"], {
type: "txt",
children: "Hi",
});
ui.showToast("Done!", "success");
ui.clear("corner_top_right");
}

onEvent and toastOn reduce the repetitive "listen and react" pattern to one line each, both returning an unsubscribe function:

import { onEvent, setNubeInstance, toastOn } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

const off = onEvent("cart:update", (state) => {
console.log("items:", state.cart.items.length);
});

// Call off() later, when you no longer need it.
void off;

toastOn("cart:add:success", "Added to cart", "success");
toastOn("cart:update", (state) => `Cart: ${state.cart.items.length} items`);
}

API Reference

Formal reference for every symbol exported from the package root (@tiendanube/nube-sdk-helper).

Instance

Manage the SDK instance used by the rest of the helper.

setNubeInstance(nube)

Registers the NubeSDK instance for the current app. Call once, at the start of App(nube).

setNubeInstance(nube: Readonly<NubeSDK>): void

getNubeInstance()

Returns the registered instance. If none was registered, it falls back to self.__SDK_INSTANCE__ and, when neither exists, throws a descriptive error.

getNubeInstance(): Readonly<NubeSDK>

clearNubeInstance()

Clears the registered instance. Useful in tests or when tearing down between app reloads.

clearNubeInstance(): void

Getters

Expose the current state and the metadata the runtime injects about the app.

getCurrentState()

Returns the current (readonly) SDK state.

getCurrentState(): Readonly<NubeSDKState>

getAppData()

Returns the app data injected by the runtime: id and script.

getAppData(): Readonly<{ id: string; script: string }>

getScriptURL()

Returns the URL the app script was loaded from, as a frozen URL instance (cached on first call).

getScriptURL(): Readonly<URL>

getScriptSearchParams()

Returns the (readonly) URLSearchParams of the script URL.

getScriptSearchParams(): Readonly<URLSearchParams>

getScriptParam(key)

Returns the value of a specific query param from the script URL, or null if absent. The idiomatic way to configure an app from the script tag without a separate request.

getScriptParam(key: string): Nullable<string>

State selectors

Focused accessors for the most-read slices of state. All take an optional state; when omitted, they read the current state from the registered instance (pass an explicit state to make them pure).

getCart(state?: NubeSDKState): Cart
getCartItems(state?: NubeSDKState): CartItem[]
getPageType(state?: NubeSDKState): Page["type"] // e.g. "home", "product", "checkout"
getCustomer(state?: NubeSDKState): Nullable<Customer> // null when unavailable on the page

Guards

Guards do double duty: they validate at runtime and narrow the type for TypeScript. Unlike a cast (as ProductPage), they check the structure before narrowing — if the check fails, the type is never exposed.

import {
getCurrentState,
isProductPage,
setNubeInstance,
} from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

const { page } = getCurrentState().location;
if (isProductPage(page)) {
// `page` is narrowed to ProductPage, so `page.data.product` is typed.
console.log(page.data.product.name);
}
}

Pages — take a Page and narrow to the matching page type:

GuardNarrows to
isProductPageProductPage
isCategoryPageCategoryPage
isCheckoutPageCheckoutPage
isAllProductsPageAllProductsPage
isSearchPageSearchPage
isHomePageHomePage
isAccountPageAccountPage
isAccountLoginPageAccountLoginPage
isAccountRegisterPageAccountRegisterPage
isAccountInfoPageAccountInfoPage
isAccountResetPageAccountResetPage
isAccountNewPasswordPageAccountNewPasswordPage
isAccountOrdersPageAccountOrdersPage

Cart — take unknown and validate the shape:

GuardNarrows to
isCartCart
isCartItemCartItem
isCartValidationSuccesssuccessful cart validation
isCartValidationPendingpending cart validation
isCartValidationFailfailed cart validation

Domain — take unknown and validate the shape:

GuardNarrows to
isStoreStore
isCustomerCustomer
isPaymentPayment
isShippingShipping
isShippingOptionShippingOption

Address — take unknown and validate the shape:

GuardNarrows to
isAddressAddress
isShippingAddressShippingAddress
isBillingAddressBillingAddress

Components and page data — useful to check the shape of page.data before accessing it:

GuardNarrows to
isNubeComponentNubeComponent
hasProductList{ products: ProductDetails[] }
hasSections{ sections: unknown[] }
isSectionWithProductsa section containing products
hasSingleProduct{ product: ProductDetails }

Page matching

pageMatch(state, handlers)

Dispatches to the handler matching the page type in the state you pass. Each handler receives the correctly-typed payload for its page. Runs once.

pageMatch(state: NubeSDKState, handlers: PageHandlers): void

onPage(handlers)

Subscribes to page:loaded and calls the matching handler on every navigation. Returns an unsubscribe function.

onPage(handlers: PageHandlers): () => void

onCheckoutStep(handlers)

Listens to checkout:ready and, when the current page is a checkout, invokes the handler registered for the current step. Also runs immediately for the current step if already in checkout. Returns an unsubscribe function.

onCheckoutStep(handlers: CheckoutStepHandlers): () => void

Related types: PageDataMap, PageHandlerFunction<T>, PageHandlers, CheckoutStepHandlers.

Render

getProductsFromState(state)

Extracts every product from the state, regardless of page type (direct lists, products inside sections, and the main product on detail pages). Returns an empty array when there are none.

getProductsFromState(state: NubeSDKState): ProductDetails[]

forEachProduct(renderFactory)

Builds a render function that extracts products from the state, maps each through renderFactory, drops null/undefined results, and auto-assigns a unique key from the product id.

forEachProduct(
renderFactory: (product: ProductDetails) => NubeComponent | null | undefined,
): (state: NubeSDKState) => NubeComponent[]

UI

ui is a frozen object with four view helpers.

ui.showToast(message: string, variant?: ToastVariant): void  // variant defaults to "info"
ui.clear(slot: UISlot): void
ui.render(slot: UISlot, component: RenderableComponent): void
ui.renderAll(slots: UISlot[], component: RenderableComponent): void
  • showToast — shows a toast in the top-right corner. variant is "success" | "error" | "warning" | "info".
  • clear — clears a slot (nube.clearSlot).
  • render — renders a component into a slot.
  • renderAll — renders the same component across multiple slots in one call.

Related types: ToastVariant, RenderableComponent, UIHelper.

Events

Ergonomic wrappers around nube.on that return an unsubscribe function.

onEvent(event, listener)

Equivalent to nube.on(event, listener), but the returned function detaches the listener via nube.off — no need to keep a reference to both the instance and the listener to clean up.

onEvent<T extends NubeSDKListenableEvent>(
event: T,
listener: EventListenerMap[T],
): () => void

toastOn(event, message, variant?)

Shows a toast whenever the event fires. The message can be a static string or a function deriving it from state. Returns an unsubscribe function.

toastOn<T extends NubeSDKListenableEvent>(
event: T,
message: string | ((state: Readonly<NubeSDKState>) => string),
variant?: ToastVariant,
): () => void

Browser

Access the Browser APIs (storage, navigation) through the SDK instance. See also Browser APIs .

browser

Object exposing the Browser APIs (asyncLocalStorage, asyncSessionStorage, navigate, ...). Materialized lazily on first property access and cached.

import { browser, setNubeInstance } from "@tiendanube/nube-sdk-helper";
import type { NubeSDK } from "@tiendanube/nube-sdk-types";

export async function App(nube: NubeSDK) {
setNubeInstance(nube); // call this first, before any other helper

await browser.asyncLocalStorage.setItem("seen-banner", "true");
const seen = await browser.asyncLocalStorage.getItem("seen-banner");
}

Navigates to a route within the store's domain. The route must start with /.

navigate(route: `/${string}`): void

clearBrowserCache()

Clears the internal Browser APIs cache, forcing a fresh instance on next access. Useful in tests.

clearBrowserCache(): void

Utilities

General-purpose functions, independent of the SDK instance.

deepClone(obj)

Deep-clones a value. Uses structuredClone when available (it is, in the NubeSDK worker runtime), falling back to JSON serialization.

deepClone<T>(obj: T): T

debounce(func, wait)

Returns a debounced version of the function that only runs after wait ms with no new calls.

debounce<T extends (...args: never[]) => unknown>(
func: T,
wait: number,
): (...args: Parameters<T>) => void

throttle(func, limit)

Returns a throttled version of the function that runs at most once every limit ms.

throttle<T extends (...args: never[]) => unknown>(
func: T,
limit: number,
): (...args: Parameters<T>) => void

Next steps

  • Events — Full list of events available in NubeSDK
  • State — NubeSDK state structure
  • UI Slots — Available slots for rendering

Help us improve NubeSDK

Found an issue or have a suggestion? Let us know on GitHub.