npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@planetdataset/sdk-hydrogen

v0.2.4

Published

React/Hydrogen wrapper for @planetdataset/sdk-core. Exports PlanetWidgetsBoot, PlanetBogo, PlanetBundle, PlanetUpsell, PlanetSmartCart, PlanetSmartCartModal, usePlanetSmartCart, planetCartApi, toLiquidCart.

Downloads

1,037

Readme

@planetdataset/sdk-hydrogen

React + Hydrogen wrapper around @planetdataset/sdk-core.

Exports:

  • Components<PlanetWidgetsBoot>, <PlanetSmartCart>, <PlanetSmartCartModal>, <PlanetBogo>, <PlanetBundle>, <PlanetUpsell>, <PlanetCartRecommendations>
  • HookusePlanetSmartCart()
  • HelpersplanetCartApi, toLiquidCart, extractNumericId

Everything is SSR-safe: the underlying UMD is dynamically imported inside useEffect, so the server bundle never touches window.

Install

npm install @planetdataset/sdk-hydrogen

@planetdataset/sdk-core is installed automatically as a transitive dependency — you don't need to add it to your package.json explicitly.

Peer dependencies: react ^18 || ^19, react-dom, @shopify/hydrogen.

Setup

1. Create a new file app/routes/cart[.]json.jsx

The SDK reads the merchant cart through fetch('/cart.json'). You need to create this file in your Hydrogen app — it doesn't exist by default. It exposes a Liquid-shaped JSON endpoint that translates Hydrogen's GraphQL cart into the format Planet widgets expect.

Paste the following content into app/routes/cart[.]json.jsx:

import {toLiquidCart} from '@planetdataset/sdk-hydrogen';

export async function loader({context}) {
  const cart = await context.cart.get();
  return Response.json(toLiquidCart(cart), {
    headers: {'Cache-Control': 'no-store'},
  });
}

The bracketed filename cart[.]json.jsx is intentional — it tells the React Router file convention to expose the route at /cart.json (a literal dot).

2. Edit app/root.jsx — boot the SDK and expose loader data

Three things to do in app/root.jsx:

a) Add the loader data

In your existing loader() function, add a planet block to the returned object:

// inside app/root.jsx

// Planet widgets render every price with the merchant's Shopify money format
// string. The Storefront API exposes it on `shop.moneyFormat`.
const SHOP_MONEY_FORMAT_QUERY = `#graphql
  query ShopMoneyFormat {
    shop {
      moneyFormat
    }
  }
`;

export async function loader(args) {
  const {storefront, env} = args.context;
  // ... your existing loader body ...

  const {shop} = await storefront.query(SHOP_MONEY_FORMAT_QUERY);

  return {
    // ... your existing returned fields ...
    planet: {
      storeDomain: env.PUBLIC_STORE_DOMAIN,
      publicAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
      shopId: env.PUBLIC_SHOP_ID,
      countryCode: storefront.i18n.country,
      languageCode: storefront.i18n.language,
      moneyFormat: shop.moneyFormat,
    },
  };
}

This object MUST be returned from the loader — it's what <PlanetWidgetsBoot> reads via useRouteLoaderData('root').

Money format (currency display)moneyFormat is the Shopify money format string (${{amount}}, €{{amount_with_comma_separator}}, {{ amount }} kr…) the widgets use to render every price. Fetching it from shop.moneyFormat keeps widget prices matching your storefront's currency symbol, separators, and decimals. Skip it and <PlanetWidgetsBoot> falls back to "${{amount}}" (US dollars) — wrong for any non-USD store. See the token reference in @planetdataset/sdk-core.

Token scopes — your PUBLIC_STOREFRONT_API_TOKEN must grant the unauthenticated Storefront API scopes the widgets use:

  • unauthenticated_read_product_listings — products & recommendations
  • unauthenticated_read_product_inventory — the recommendations widget filters on availableForSale; without it products read as unavailable and nothing renders
  • unauthenticated_read_metaobjects — Smart Cart / cart-recommendations config (the metaobject definitions must also be exposed as PUBLIC_READ)
  • unauthenticated_read_selling_plans — only if you use subscriptions

b) Mount <PlanetWidgetsBoot> (mandatory)

In the default App component, add the boot component. This step is required — without it, no Planet widget can render or talk to your cart.

import {PlanetWidgetsBoot} from '@planetdataset/sdk-hydrogen';

export default function App() {
  const data = useRouteLoaderData('root');
  return (
    <Analytics.Provider cart={data.cart} shop={data.shop}>
      <PageLayout {...data}>
        <PlanetWidgetsBoot
          storeDomain={data.planet.storeDomain}
          publicAccessToken={data.planet.publicAccessToken}
          shopId={data.planet.shopId}
          countryCode={data.planet.countryCode}
          languageCode={data.planet.languageCode}
          moneyFormat={data.planet.moneyFormat}
        />
        <Outlet />
      </PageLayout>
    </Analytics.Provider>
  );
}

<PlanetWidgetsBoot> uses the default planetCartApi automatically (POSTs to /cart, reads /cart.json) — no need to pass a cart prop unless you want to customize it.

c) Mount <PlanetSmartCartModal> (optional — only if you use the cart drawer)

If you plan to use the Smart Cart drawer (the slide-in panel that opens after add-to-cart and that usePlanetSmartCart() controls), add <PlanetSmartCartModal /> inside the <body> of the Layout component, and import the SDK styles:

import {PlanetWidgetsBoot, PlanetSmartCartModal} from '@planetdataset/sdk-hydrogen';
import planetSdkStyles from '@planetdataset/sdk-core/style.css?url';

export function Layout({children}) {
  return (
    <html>
      <head>
        <link rel="stylesheet" href={planetSdkStyles} />
        {/* ... your existing head content ... */}
      </head>
      <body>
        {children}
        <PlanetSmartCartModal />
      </body>
    </html>
  );
}

Skip this section entirely if you only want product-page widgets (BOGO / Bundle / Upsell) and you'll handle the post-add-to-cart UX yourself (e.g. redirect to /cart).

3. Use widgets on product pages

import {PlanetBogo, PlanetBundle, PlanetUpsell} from '@planetdataset/sdk-hydrogen';

export default function Product() {
  const {product} = useLoaderData();
  const selectedVariant = useOptimisticVariant(product.selectedOrFirstAvailableVariant);
  return (
    <>
      {/* Your own product UI */}
      <PlanetBogo productId={product.id} variantId={selectedVariant.id} />
      <PlanetBundle productId={product.id} variantId={selectedVariant.id} />
      <PlanetUpsell productId={product.id} variantId={selectedVariant.id} />
    </>
  );
}

Accepts both Shopify GIDs (gid://shopify/Product/123) and numeric strings — the wrappers normalize.

4. Open the cart from a button (header, hero CTA, etc.)

Optional — requires <PlanetSmartCartModal /> mounted (step 2c).

import {usePlanetSmartCart} from '@planetdataset/sdk-hydrogen';

function CartButton({count}) {
  const {open, close, toggle} = usePlanetSmartCart();
  return (
    <button onClick={() => open()}>
      Cart ({count})
    </button>
  );
}

The drawer opens itself automatically after add-to-cart from a widget — you don't need to wire it up. Use the hook only for explicit user actions outside a widget context.

5. Show cart recommendations (the Smart Cart upsell, standalone)

<PlanetCartRecommendations> renders the same product-recommendation block as the Smart Cart drawer, but as a standalone widget you can place anywhere (cart page, custom drawer, etc.). Settings come from the merchant's Smart Cart config (fetched from the Storefront API) — no settings prop needed.

Requires the /cart.json route (Setup → step 1). The widget reads the cart (via the SDK cart API) to compute recommendations from the products in it — without that route it can't render. It's part of the mandatory <PlanetWidgetsBoot> setup, and the Smart Cart needs it too.

Pass the current Hydrogen cart (GraphQL shape — the resolved/optimistic cart from your loader). The wrapper pushes it into the widget on every render, so the recommendations stay reactive to every cart change (add, remove, quantity) with instant optimistic updates:

import {PlanetCartRecommendations} from '@planetdataset/sdk-hydrogen';

function CartRecommendations({cart}) {
  return <PlanetCartRecommendations cart={cart} />;
}

Why pass cart? Without it, the widget's only sync path is the SDK's internal cart-change events — fired by the widget's own recommended-product add, adds routed through PlanetWidgets.triggerAddToCart, or Planet smart-cart drawer ops. It does not react to mutations made directly through Hydrogen's native <CartForm> (remove a line, change quantity…), and <PlanetWidgetsBoot>'s revalidation listens to the same events (not CartForm). Passing cart is the reliable way to stay fully reactive.

Component reference

<PlanetWidgetsBoot>

Initializes the SDK on the client. Renders nothing. Mount once at the root, inside any context providers your cart adapter needs.

| Prop | Type | Default | |---|---|---| | storeDomain | string | required | | publicAccessToken | string | required | | shopId | string | required | | countryCode | string | required | | languageCode | string | required | | moneyFormat | string | "${{amount}}" | | advancedOptionsValues | object | sensible defaults derived from country code | | cart | CartApi | planetCartApi (POST /cart, GET /cart.json) | | revalidateCartOnChange | boolean | true |

moneyFormat is the Shopify money format string used to render all widget prices — fetch it from shop.moneyFormat in your root loader and pass it through (see Setup → step 2). It defaults to US dollars ("${{amount}}"), so non-USD stores must set it or prices show the wrong currency. Token reference: @planetdataset/sdk-core.

It also revalidates your loader cart data whenever a Planet widget mutates the cart (e.g. an add from <PlanetCartRecommendations>, which bypasses Hydrogen's CartForm), so the native cart drawer and badge refresh automatically — no PlanetCartSync component to write. Set revalidateCartOnChange={false} to opt out.

<PlanetSmartCartModal> / <PlanetSmartCart>

Drawer modal wrapper (mounts once at root) and drawer body (rarely used directly).

| Prop | Type | |---|---| | className | string | | style | React.CSSProperties |

<PlanetBogo> / <PlanetBundle> / <PlanetUpsell>

Product-page widgets.

| Prop | Type | Notes | |---|---|---| | productId | string \| number | GID or numeric — auto-normalized | | variantId | string \| number | GID or numeric — auto-normalized | | className | string | | | style | React.CSSProperties | |

<PlanetCartRecommendations>

The Smart Cart product-recommendation upsell as a standalone widget. Settings are fetched from the merchant's Smart Cart config (Storefront API), so there's no settings prop.

| Prop | Type | Notes | |---|---|---| | cart | object | Hydrogen GraphQL cart — converted via toLiquidCart and pushed in. Optional but recommended for host-driven sync | | className | string | | | style | React.CSSProperties | |

usePlanetSmartCart()

{
  open: (refresh?: boolean) => Promise<void>;
  close: () => Promise<void>;
  toggle: () => Promise<void>;
}

refresh: true re-fetches the cart before opening (useful after external mutations).

planetCartApi

Default cart adapter used by <PlanetWidgetsBoot>. Exports if you want to compose / wrap it:

import {planetCartApi} from '@planetdataset/sdk-hydrogen';

const customCart = {
  ...planetCartApi,
  add: async (items) => {
    const cart = await planetCartApi.add(items);
    trackAnalytics(items);
    return cart;            // ← MUST return the cart
  },
};

Warning — if you wrap add, you must await and return the promise. Otherwise the smart-cart drawer opens before /cart.json reflects the new line, and downstream consumers crash on cart().items.

toLiquidCart(hydrogenCart)

Hydrogen GraphQL cart → Liquid /cart.js shape. Used internally by the /cart.json route. Re-exported so you can use it in your own endpoints.

extractNumericId(gid)

gid://shopify/Foo/123"123". Returns empty string for falsy input.

Caveats

  • Upsell type: "checkbox" is Liquid-only — that mode injects variant IDs into a native Shopify <form>, which doesn't exist in Hydrogen. Use type: "radio" or type: "single" for headless storefronts.
  • <planet-upsell-popup> isn't yet exposed via this package (next iteration).

Versioning

0.x.y — pre-1.0. APIs may change. Pin a minor version (^0.1.0 rather than latest).