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, ui } from "@tiendanube/nube-sdk-helper";

// Anywhere 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 } from "@tiendanube/nube-sdk-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, getPageType, getCustomer } from "@tiendanube/nube-sdk-helper";

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

// With an explicit state: pure, ideal for unit tests.
const itemsFromMock = getCartItems(mockState);
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 } from "@tiendanube/nube-sdk-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 { getScriptURL, getScriptParam } from "@tiendanube/nube-sdk-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 { pageMatch, getCurrentState } from "@tiendanube/nube-sdk-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 } from "@tiendanube/nube-sdk-helper";

const stop = onPage({
product: (state, product) => trackProductView(product.id),
checkout: (state, checkout) => {
if (checkout.step === "success") trackPurchase();
},
// category / home handlers are optional
});

// Later, when you no longer need it:
stop();

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

import { onCheckoutStep } from "@tiendanube/nube-sdk-helper";

onCheckoutStep({
success: () => trackPurchase(),
});

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 { getNubeInstance, forEachProduct } from "@tiendanube/nube-sdk-helper";

getNubeInstance().render(
"product_grid_item_image_bottom_right",
forEachProduct((product) => <MyBadge product={product} />),
);

Without JSX, the factory returns a component object directly:

getNubeInstance().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 { ui } from "@tiendanube/nube-sdk-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, toastOn } from "@tiendanube/nube-sdk-helper";

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

// Later, when you no longer need it:
off();

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

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.