Migrating Your App to NubeSDK
This guide shows you how to convert a classic Tiendanube/Nuvemshop script — one that uses document, window, jQuery, or direct DOM manipulation — into a NubeSDK app that runs inside a secure Web Worker.
Each section presents a before (legacy) and after (NubeSDK) comparison so you can map your existing code to the new model.
Key differences at a glance
NubeSDK apps run inside a Web Worker. There is no access to document, window, or any DOM API.
UI is rendered declaratively into predefined slots using JSX or the declarative API.
Communication with the store/checkout happens through events, not DOM listeners.
Storefront apps require the Patagonia theme. Checkout apps work on all themes.
1. Entry point
Every NubeSDK app exports a single App function that receives the nube instance. This replaces your legacy IIFE, DOMContentLoaded, or global script.
// Legacy: script runs directly in the page
(function () {
document.addEventListener("DOMContentLoaded", () => {
// your app logic
});
})();
import type { NubeSDK } from "@tiendanube/nube-sdk-types";
export function App(nube: NubeSDK) {
// your app logic — executed automatically by the SDK
}
Use npm create nube-app@latest to scaffold a project that is already configured with the right entry point, build tool, and types. See Getting Started for details.
2. Reacting to page or route changes
Legacy scripts typically check window.location or listen to popstate. In NubeSDK you listen to events that tell you exactly which page the user is on and what data is available.
// Legacy: check URL to know which page you're on
if (window.location.pathname.includes("/product/")) {
// product page logic
}
nube.on("location:updated", ({ location }) => {
if (location.page?.type === "product") {
const product = location.page.data.product;
console.log("Product page:", product.name);
}
});
For checkout apps, listen to checkout:ready instead:
nube.on("checkout:ready", ({ location }) => {
const { page } = location;
if (page.type === "checkout" && page.data.step === "start") {
// Checkout start page — contact form, address, shipping
}
if (page.type === "checkout" && page.data.step === "payment") {
// Payment step
}
});
Check the available events in the events documentation .
3. Listening to user interactions
In legacy scripts you'd query the DOM for elements and attach event listeners. NubeSDK provides store events instead — you react to data changes, not clicks on particular elements.
// Legacy: click handler on a DOM element
document.querySelector(".buy-button").addEventListener("click", () => {
// logic when buy button is clicked
});
// NubeSDK: react when the cart changes
nube.on("cart:update", ({ cart }) => {
console.log("Cart now has", cart.items.length, "items");
console.log("Total:", cart.prices.total);
});
There is no 1:1 equivalent of DOM click handlers. Instead, map your logic to the data change that the click produces. A click on "Buy" produces a cart:update; a click on "Select shipping" produces a shipping:update, etc.
4. Injecting UI into the page
Instead of creating elements with document.createElement or injecting HTML with innerHTML, you render components into slots.
// Legacy: inject a banner after the line items
const banner = document.createElement("div");
banner.innerHTML = '<p style="padding:16px">Thank you!</p>';
document.querySelector(".line-items")?.after(banner);
import { Box, Text } from "@tiendanube/nube-sdk-jsx";
nube.render("after_line_items", () => (
<Box padding="16px">
<Text>Thank you for your purchase!</Text>
</Box>
));
You can also use the declarative API (no JSX required):
import { box, txt } from "@tiendanube/nube-sdk-ui";
nube.render("after_line_items", () =>
box({
padding: "16px",
children: [txt({ children: "Thank you for your purchase!" })],
}),
);
Don't know which slot to use? Check Checkout Slots and Storefront Slots for a full visual reference.
5. Rendering dynamic UI based on state
In legacy apps you'd read DOM values or make API calls and then update elements. In NubeSDK, nube.render() can receive a function — the SDK calls it with the current state every time it changes.
// Legacy: read cart total from DOM and show a message
const totalEl = document.querySelector(".cart-total");
const total = parseFloat(totalEl?.textContent?.replace("$", "") ?? "0");
if (total > 100) {
const msg = document.createElement("p");
msg.textContent = "You qualify for free shipping!";
totalEl?.after(msg);
}
import { Text } from "@tiendanube/nube-sdk-jsx";
nube.render("after_line_items", (state) => {
if (state.cart.prices.total > 100) {
return <Text>You qualify for free shipping!</Text>;
}
return null;
});
6. Styling
Legacy apps often inject <style> tags or add inline styles. NubeSDK provides styled(), StyleSheet.create(), and a theme system that adapts to each store.
// Legacy: inject custom styles into the page
const style = document.createElement("style");
style.textContent = `
.my-badge {
background: #e91e63;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
`;
document.head.appendChild(style);
const badge = document.createElement("span");
badge.className = "my-badge";
badge.textContent = "NEW";
document.querySelector(".product-title")?.prepend(badge);
import { Text } from "@tiendanube/nube-sdk-jsx";
import { styled, theme } from "@tiendanube/nube-sdk-ui";
const Badge = styled(Text)`
background: ${theme.color.accent};
color: white;
padding: 4px 8px;
border-radius: ${theme.border.radius};
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
`;
nube.render("before_product_detail_name", () => <Badge>NEW</Badge>);
Use theme tokens instead of hard-coded colours — your component will automatically match the store's theme. See Styling for the complete token list.
7. Reading and reacting to cart data
// Legacy: scrape the cart total from the page
const priceText = document.querySelector(".total-price")?.textContent;
const total = parseFloat(priceText?.replace(/[^0-9.]/g, "") ?? "0");
console.log("Cart total:", total);
// NubeSDK: cart data is available in the state
const { cart } = nube.getState();
console.log("Cart total:", cart.prices.total);
console.log("Items:", cart.items.length);
console.log("Subtotal:", cart.prices.subtotal);
console.log("Shipping:", cart.prices.shipping);
Or react to changes over time:
nube.on("cart:update", ({ cart }) => {
console.log("Cart changed — new total:", cart.prices.total);
});
8. Validating the cart / blocking checkout
Legacy scripts would prevent form submission or add alerts. NubeSDK uses a formal config + validate flow.
// Legacy: block the checkout form from submitting
document.querySelector("#checkout-form")
?.addEventListener("submit", (e) => {
const items = document.querySelectorAll(".cart-item").length;
if (items < 3) {
e.preventDefault();
alert("You need at least 3 items to continue");
}
});
// Step 1: Tell NubeSDK your app validates the cart
nube.send("config:set", () => ({
config: { has_cart_validation: true },
}));
// Step 2: React to cart changes and send validation result
nube.on("cart:update", ({ cart }) => {
const status = cart.items.length < 3 ? "fail" : "success";
const reason = status === "fail"
? "You need at least 3 items to continue"
: undefined;
nube.send("cart:validate", () => ({
cart: { validation: { status, reason } },
}));
});
9. Making HTTP requests
Legacy scripts use XMLHttpRequest or fetch on the main thread. The Worker environment supports fetch natively.
// Legacy: fetch data and inject into the page
fetch("https://api.example.com/promotions")
.then((res) => res.json())
.then((data) => {
const el = document.createElement("div");
el.textContent = data.message;
document.body.appendChild(el);
});
import { Text } from "@tiendanube/nube-sdk-jsx";
export function App(nube: NubeSDK) {
fetch("https://api.example.com/promotions")
.then((res) => res.json())
.then((data) => {
nube.render("before_main_content", () => (
<Text>{data.message}</Text>
));
});
}
10. Using localStorage / sessionStorage
Web Workers don't have direct access to localStorage. NubeSDK provides async equivalents.
// Legacy: store a flag in localStorage
localStorage.setItem("promo_seen", "true");
const seen = localStorage.getItem("promo_seen");
const browser = nube.getBrowserAPIs();
// Store a value (returns a Promise)
await browser.asyncLocalStorage.setItem("promo_seen", "true");
// Retrieve it
const seen = await browser.asyncLocalStorage.getItem("promo_seen");
// Store a value that expires in 60 seconds
await browser.asyncLocalStorage.setItem("promo_seen", "true", 60);
11. Navigating programmatically
// Legacy: redirect to a different page
window.location.href = "/products";
const browser = nube.getBrowserAPIs();
browser.navigate("/products");
navigate only works within the current domain. External URLs are not supported.
12. Reacting to shipping and payment changes
// Legacy: detect shipping option changes
document.querySelector("#shipping-options")
?.addEventListener("change", (e) => {
console.log("Shipping changed to:", e.target.value);
});
nube.on("shipping:update", ({ shipping }) => {
if (shipping?.selected) {
console.log("Shipping method changed to:", shipping.selected);
}
});
nube.on("payment:update", ({ payment }) => {
console.log("Payment method changed to:", payment?.selected?.name);
});
13. Reacting to customer data
// Legacy: read customer email from a form field
const email = document.querySelector("#contact-email")?.value;
console.log("Customer email:", email);
nube.on("customer:update", ({ customer }) => {
console.log("Customer email:", customer?.contact?.email);
console.log("Shipping address:", customer?.shipping_address);
});
14. Adding items to the cart
// Legacy: submit a form to add to cart
Tiendanube.addToCart({ variant_id: 123, quantity: 1 });
nube.send("cart:add", () => ({
cart: {
items: [{ variant_id: 123, product_id: 321, quantity: 1 }],
},
}));
nube.on("cart:add:success", ({ cart }) => {
console.log("Item added!");
});
nube.on("cart:add:fail", ({ cart }) => {
console.log("Failed to add item");
});
15. Order tracking (checkout success page)
// Legacy: scrape order data from the success page
const orderId = document.querySelector(".order-id")?.textContent;
analytics.track("purchase", { orderId });
nube.on("order:update", ({ order }) => {
console.log("Order completed:", order.id);
// Send to analytics, fire conversion pixels, etc.
});
What you can't do in NubeSDK
Because your code runs in a Web Worker, these APIs are not available:
| Legacy API | Why it's not available | Alternative |
|---|---|---|
document / window | No DOM in workers | Use nube.render() and events |
localStorage / sessionStorage | Main-thread API | nube.getBrowserAPIs().asyncLocalStorage |
window.location.href = ... | Main-thread API | nube.getBrowserAPIs().navigate(...) |
React, Vue, Angular | No DOM rendering | Use NubeSDK components (JSX or declarative) |
jQuery | No DOM | Use events and nube.render() |
XMLHttpRequest | Available but prefer | fetch() (works in workers) |
Migration checklist
Use this list to track your progress:
- Created a project with
npm create nube-app@latest - Main entry file exports
App(nube: NubeSDK) - All UI is rendered through
nube.render()— nodocument.*orinnerHTML - All logic uses
nube.on,nube.send,nube.getState - No references to
window,document, jQuery, React, or other DOM libraries - localStorage replaced with
asyncLocalStorage/asyncSessionStorage - Styling uses
styled(),StyleSheet.create(), or theme tokens — no<style>injection - Tested in dev mode (
npm run dev) with the script pointing tolocalhost:8080/main.min.js - "Uses NubeSDK" flag enabled in the Partner Portal
- App works on Patagonia theme (storefront) or any theme (checkout)
Useful resources
- Script Structure — How
App(nube)works - Events — Full list of events
- State — Complete state reference
- Components — Available UI components
- Slots: Checkout — Checkout slot reference
- Slots: Storefront — Storefront slot reference
- Styling —
styled(),StyleSheet, and theme tokens - Browser APIs — localStorage, navigate, postMessage
- Examples — Complete working examples
- NubeSDK Assistant — AI assistant for code conversion
Need help? Contact api@tiendanube.com / api@nuvemshop.com.br . Missing a component, slot, or event? Email with details and visual references.