@planetdataset/sdk-core
v0.2.4
Published
Vanilla-JS SDK for embedding Planet widgets (BOGO, Bundle, Upsell, Smart-cart) on any storefront. Sets up window.PlanetWidgets with init() and triggerAddToCart().
Readme
@planetdataset/sdk-core
Vanilla-JS SDK for embedding Planet widgets on any storefront — Shopify Hydrogen, custom Next.js, plain HTML, etc.
The package ships a single UMD bundle (planet-sdk.umd.js, ~460 KB minified / ~117 KB gzipped) that attaches window.PlanetWidgets on load. After calling init(config) once on the client, drop the planet-* web components into your markup.
For React/Hydrogen merchants, use @planetdataset/sdk-hydrogen instead — it provides typed React wrappers, a Hydrogen cart adapter, and SSR-safe loading on top of this package.
Install
npm install @planetdataset/sdk-coreInitialize
import "@planetdataset/sdk-core";
import "@planetdataset/sdk-core/style.css";
window.PlanetWidgets.init({
// Required
storeDomain: "my-store.myshopify.com",
publicAccessToken: "<Storefront API public access token>",
shopId: "gid://shopify/Shop/12345",
localization: { country: "US", language: "EN" },
moneyFormat: "${{amount}}", // Shopify money format string — see "Money format" below
// Customer-targeting context for widget visibility rules
advancedOptionsValues: {
"customer.has_account": false,
"customer.total_spent": 0,
"customer.tags": [],
"customer.orders_count": 0,
"localization.market.handle": "us",
"product.selling_plan_groups": false,
},
// Cart callbacks (Liquid `/cart.js` shape — see below)
cart: {
get: async () => /* fetch current cart */,
add: async (items) => /* add lines, return updated cart */,
update: async (updates) => /* bulk update line quantities, return cart */,
change: async (lineKey, quantity) => /* change single line, return cart */,
},
// Optional: smart-cart drawer configuration
smartCart: {
cartUrl: "/cart", // form action for the checkout button
customSlots: { /* ... */ },
hooks: {
transformLineItems: (items) => items,
afterCartChange: (cart, refetch) => { /* ... */ },
},
},
});init() is synchronous and idempotent. Call window.PlanetWidgets.isInitialized() to check, or onReady(cb) to register a callback that fires once the SDK is ready (useful when widgets are rendered before init resolves).
Money format
Every price the widgets render — BOGO / Bundle / Upsell prices, Smart Cart totals, discount amounts, free-gift thresholds — is formatted with moneyFormat. It's the Shopify money format string, the exact same value a theme uses for shop.money_format: a template with one {{ amount }}-style placeholder plus whatever literal currency symbol or code the merchant puts around it (e.g. ${{amount}}, €{{amount_with_comma_separator}}, {{ amount }} USD, {{ amount_no_decimals }} kr).
Pass the merchant's real format — it's not decoration. The placeholder token selects the thousands/decimal separators and the number of decimals, and the surrounding text is the currency symbol. The default "${{amount}}" renders US-style $1,134.65; a EUR store that leaves it unset shows a $ instead of € with the separators swapped. Omit it and prices are wrong for every non-USD merchant.
Supported placeholder tokens
The token drives separators and decimals (example amount: 1134.65):
| Token | Renders |
|---|---|
| {{amount}} | 1,134.65 |
| {{amount_no_decimals}} | 1,135 |
| {{amount_with_comma_separator}} | 1.134,65 |
| {{amount_no_decimals_with_comma_separator}} | 1.135 |
| {{amount_with_apostrophe_separator}} | 1'134.65 |
| {{amount_with_space_separator}} | 1 134,65 |
| {{amount_no_decimals_with_space_separator}} | 1 135 |
| {{amount_with_period_and_space_separator}} | 1 134.65 |
Any literal characters around the token are kept verbatim, so ${{amount}} → $1,134.65 and {{amount}} USD → 1,134.65 USD.
Where to get it
Read the format from whatever host renders the page — all three sources return the same string:
- Shopify Liquid theme —
{{ shop.money_format }}(or{{ shop.money_with_currency_format }}to include the ISO code):window.PlanetWidgets.init({ /* … */ moneyFormat: {{ shop.money_format | json }} }); - Storefront API (Hydrogen, custom React/Next.js, any headless storefront) — the
Shop.moneyFormatfield:query { shop { moneyFormat } } - Admin API (server side) —
shop.currencyFormats.moneyFormat(andmoneyWithCurrencyFormatfor the currency-code variant).
For Hydrogen,
@planetdataset/sdk-hydrogenwiresshop.moneyFormatthrough the root loader for you — see its README.
Storefront API token scopes
The publicAccessToken must grant these unauthenticated Storefront API scopes:
| Scope | Why |
|---|---|
| unauthenticated_read_product_listings | Products & recommendations |
| unauthenticated_read_product_inventory | Required — the recommendations widget filters on availableForSale; without this scope products read as unavailable and nothing renders |
| unauthenticated_read_metaobjects | Smart Cart / cart-recommendations config (settings). The metaobject definitions must also expose Storefront access (PUBLIC_READ). |
| unauthenticated_read_selling_plans | Only if you use subscriptions / selling plans |
Web components
After init, place these tags in your markup. They accept Shopify numeric IDs (not GIDs):
| Tag | Purpose | Attributes |
|---|---|---|
| <planet-bogo> | Buy-One-Get-One offers | product-id, variant-id |
| <planet-bundle> | Multi-product bundles | product-id, variant-id |
| <planet-upsell> | Recommended products with checkbox/radio selection | product-id, variant-id |
| <planet-cart-recommendations> | Cart recommendations — the Smart Cart upsell as a standalone widget | none required (see note) |
| <planet-smart-cart> | Drawer body — usually mounted inside planet-smart-cart-modal | none |
| <planet-smart-cart-modal> | Slide-in drawer wrapper. Mounts once at the root of your app, listens for ATC events, registers window.PlanetDataset.SmartCart.methods.openDrawer | none |
Example product page:
<planet-bogo product-id="1234567890" variant-id="9876543210"></planet-bogo>
<planet-bundle product-id="1234567890" variant-id="9876543210"></planet-bundle>
<planet-upsell product-id="1234567890" variant-id="9876543210"></planet-upsell>Example root layout (drop once globally):
<planet-smart-cart-modal></planet-smart-cart-modal><planet-cart-recommendations>
The Smart Cart product-recommendation upsell, usable on its own (cart page, custom drawer, etc.). It pulls its settings from the same Smart Cart metaobject config as <planet-smart-cart> (via the Storefront API — see Smart-cart configuration), so it needs no settings attribute.
Requires the
cartAPI you pass toinit()(see Cart API contract). The widget reads the cart viacart.get()to compute recommendations from the products in it — without a working cart API it can't render.
It self-syncs on the SDK's own cart-change events: it refetches via the cart API from init() after its own add-to-cart, an add via PlanetWidgets.triggerAddToCart, or a Smart Cart drawer op. It does not see cart mutations you make outside the SDK — for those, push the current cart as a cart JS property (Liquid /cart.js shape) whenever your cart changes (set it as a property, not an attribute, since it's an object):
const el = document.querySelector("planet-cart-recommendations");
el.cart = currentCart; // ICart, Liquid /cart.js shapeIn React/Hydrogen, @planetdataset/sdk-hydrogen's <PlanetCartRecommendations cart={...} /> handles this for you.
Cart API contract
The cart callbacks must return data in the Liquid /cart.js shape:
interface ICart {
token: string;
items: Array<{
key: string;
id: string; // variant numeric ID
variant_id: string;
product_id: string;
quantity: number;
title: string;
product_title: string;
variant_title: string;
vendor: string;
url: string; // "/products/<handle>?variant=<id>"
image: string; // URL
featured_image: { url: string; alt: string; aspect_ratio: number | null } | null;
price: number; // cents
final_price: number;
line_price: number;
final_line_price: number;
properties: Record<string, string>;
selling_plan_allocation: object | null;
line_level_discount_allocations: Array<{ amount: number; discount_application: { title: string } }>;
parent_relationship: object | null;
}>;
item_count: number;
total_price: number; // cents
total_discount: number;
currency: string; // "USD"
note: string | null;
cart_level_discount_applications: Array<object>;
}The add(items) callback receives AddToCartItem[]:
interface AddToCartItem {
id: string; // variant numeric ID
quantity: number;
properties?: Record<string, string>;
selling_plan?: string;
parent_id?: string;
}update(updates) receives a Record<lineKey, quantity>. change(lineKey, quantity) mutates a single line (quantity 0 = remove).
All four return Promise<ICart>.
Trigger add-to-cart programmatically
await window.PlanetWidgets.triggerAddToCart([
{ id: "9876543210", quantity: 1 },
]);This dispatches BEFORE_ADD_TO_CART on the internal AsyncEventBus (so the smart-cart drawer opens) and routes through cart.add().
Coordination model
When ATC fires from any widget (BOGO, Bundle, Upsell, native form, programmatic call), the SDK dispatches planet:add-to-cart:before on an internal AsyncEventBus. Smart-cart-modal listens, performs the add via cart.add(), and opens the drawer. If smart-cart is not mounted, the widget's own cart.add() call still runs.
The drawer also exposes window.PlanetDataset.SmartCart.methods.openDrawer(open, refetch) once <planet-smart-cart-modal> has mounted, so you can open/close it from your own UI (e.g. a cart icon button).
Smart-cart configuration
Smart-cart pulls its display configuration (cart title, slot text, free-gift rules, recommendation algorithm, etc.) from Shopify metaobjects via the Storefront API:
planet_smart_cart_config— main config (cart enabled, position, theme)planet_smart_cart_general_content— generic UI stringsplanet_smart_cart_modules_content— per-module strings (free-gift, recommendations, etc.)
These metaobjects are managed in the Planet admin app. If a merchant hasn't configured them, smart-cart renders nothing (graceful degradation).
Build target
- Output: UMD, single file, all dependencies (SolidJS, corvu, storefront-api-client) bundled
- Target: ES2022
- CSS: ships as a sibling
planet-sdk.css— import via@planetdataset/sdk-core/style.css
Versioning
0.x.y — pre-1.0. Web component tag names, JS API and metaobject contract may evolve until 1.0.
