NUMU Docs
Contact
APIThemes
Partner Apps
APIThemes
Partner Apps
  1. Theme SDK
  • 🗂️ Themes overview
  • 🚀 Start here
  • Glossary
  • Theme engine
    • Architecture
    • BYOT contract
    • Customizer
    • Federation runtime
    • Page data contract
    • Theme manifest
  • Theme SDK
    • Components
    • Federation helpers
    • Hooks
    • SDK overview
    • Type definitions
  • CLI & Vite plugin
    • CLI commands
    • CLI overview
    • Lint rules
    • Section library
    • Vite plugin
  • Storefront host
    • API proxies
    • Built-in fallbacks
    • BYOT fork
    • Routing
    • Storefront overview
  1. Theme SDK

Hooks

Every public hook in @numueg/theme-sdk, grouped by surface. Each entry: signature, return shape, when to use, gotchas.

Shop + theme#

useShop()#

function useShop(): {
  id: string;
  name: string;
  subdomain: string;
  domain: string;
  currency: string;
  default_language: "en" | "ar";
  logo_url: string | null;
  formatUrl: (path: string) => string;   // /products/x → https://<sub>.numueg.app/products/x
};
The current store. Always available — <NuMuProvider> populates it from ctx.store.
function Header() {
  const shop = useShop();
  return (
    <header className="flex justify-between p-4">
      {shop.logo_url ? (
        <img src={shop.logo_url} alt={shop.name} className="h-10" />
      ) : (
        <span className="font-bold text-xl">{shop.name}</span>
      )}
      <a href={shop.formatUrl("/cart")}>Cart</a>
    </header>
  );
}
// Subdomain-aware canonical URL for SEO
function CanonicalLink({ path }: { path: string }) {
  const shop = useShop();
  return <link rel="canonical" href={shop.formatUrl(path)} />;
}

useThemeSettings()#

function useThemeSettings(): ThemeSettingsV3;
Resolved customizer state (theme-level + section-level). For section-level values, prefer useSection() — it's scoped.

useLocalization() / useDirection() / useTranslation()#

function useLocalization(): { locale: "en" | "ar"; supportedLocales: string[] };
function useDirection(): "ltr" | "rtl";
function useTranslation(): (key: string, params?: Record<string, string>) => string;
const t = useTranslation();
return <button>{t("product.addToCart")}</button>;
Keys come from your locales/<lang>.json. Falls back to the default locale, then to the key string.

useCurrency()#

function useCurrency(): {
  currency: string;
  rate: number;             // multiplier vs store currency
  symbol: string;
  format: (cents: number) => string;
};
Phase 6 multi-currency presentment. Default is the store currency at rate 1.0.

Page#

usePage()#

function usePage(): {
  type: string;             // "home" | "product" | "cart" | ...
  title?: string;
  handle?: string;
  data?: Record<string, unknown>;
};
Use a switch on page.type in your main.tsx to route. The discriminated union narrows data per branch.

Products#

useProduct() / useProductOptional()#

function useProduct(): Product;                  // throws if not in <ProductProvider>
function useProductOptional(): Product | null;
Use the Optional variant on cards that might render outside a product context.

useProducts(opts?)#

function useProducts(opts?: {
  limit?: number;
  page?: number;
  category_id?: string;
  search?: string;
}): {
  items: Product[];
  total: number;
  loading: boolean;
  error: Error | null;
};
Calls /api/storefront/products. Server-side cached for 60s.

useVariantSelection(product)#

Phase 8.1 — PDP variant picker state.
function useVariantSelection(
  product: Pick<Product, "options" | "variants">,
  opts?: { autoSelect?: boolean }
): {
  selection: Record<string, string>;
  variant: ProductVariant | null;
  select: (axis: string, value: string) => void;
  reset: () => void;
  availability: Record<string, Set<string>>;
  isComplete: boolean;
};
Auto-selects the default in-stock variant on mount (Shopify default). availability lets you grey out swatches whose paired variants are all sold out for the current locked axes.
const { selection, variant, select, availability, isComplete } = useVariantSelection(product);

return (
  <>
    {product.options.map(axis => (
      <div key={axis.name}>
        <label>{axis.name}: {selection[axis.name]}</label>
        {(axis.values || []).map(v => (
          <button
            key={v}
            disabled={!availability[axis.name].has(v)}
            onClick={() => select(axis.name, v)}
          >{v}</button>
        ))}
      </div>
    ))}
    <button disabled={!isComplete || !variant?.is_in_stock}>
      {variant ? `Add — ${variant.price}` : "Choose options"}
    </button>
  </>
);

useRelatedProducts(productId, opts?)#

function useRelatedProducts(
  productId: string | null | undefined,
  opts?: { limit?: number }
): { items: Product[]; loading: boolean; error: Error | null };
Same-category siblings. Returns [] (not error) when the endpoint is missing — themes branch on items.length to render the section or skip.

Collections#

useCollection() / useCollectionOptional() / useCollections()#

Same shape as the product hooks. useCollections() lists all categories.

Cart#

useCart()#

function useCart(): {
  cart: Cart;
  loading: boolean;
  addItem: (productId: string, variantId?: string | null, quantity?: number) => Promise<void>;
  removeItem: (itemId: string) => Promise<void>;
  updateQuantity: (itemId: string, quantity: number) => Promise<void>;
  applyDiscount: (code: string) => Promise<void>;
  removeDiscount: () => Promise<void>;
  updateNote: (note: string) => Promise<void>;
  clearCart: () => Promise<void>;
};
Mutations call /api/cart/* proxies. They auto-refresh cart on success and dispatch a numu:cart:updated window event for non-React listeners (header cart badge etc.).
Idempotency-key headers are stamped automatically — double-clicks won't double-add.

Customer#

useCustomer()#

function useCustomer(): Customer | null;
null for guests. <NuMuProvider> reads customer_access_token from cookies on mount.

useCustomerActions()#

function useCustomerActions(): {
  login: (email: string, password: string) => Promise<...>;
  register: (data: RegisterData) => Promise<...>;
  logout: () => Promise<void>;
  requestRecover: (email: string) => Promise<...>;
  confirmReset: (token: string, password: string) => Promise<...>;
  updateProfile: (patch: Partial<Customer>) => Promise<...>;
  changePassword: (currentPassword: string, newPassword: string) => Promise<...>;
};
All return discriminated { ok: true, data } | { ok: false, error } so themes can render field-level errors without try/catch.

useCustomerAddresses()#

function useCustomerAddresses(): {
  addresses: Address[];
  loading: boolean;
  addAddress: (a: Omit<Address, "id">) => Promise<Address>;
  updateAddress: (id: string, patch: Partial<Address>) => Promise<Address>;
  deleteAddress: (id: string) => Promise<void>;
  setDefaultAddress: (id: string) => Promise<void>;
};

useOrders(opts?) / useOrder(id) / useReorder()#

function useOrders(opts?: { page?: number; limit?: number }): { items: Order[]; total: number; loading: boolean; };
function useOrder(id: string): { order: Order | null; loading: boolean; };
function useReorder(): {
  result: ReorderResult | null;
  loading: boolean;
  error: Error | null;
  reorder: (orderId: string) => Promise<ReorderResult | null>;
  reset: () => void;
};
ReorderResult.skipped[] includes per-line reasons (product_deleted, out_of_stock, etc.) — themes show them in a banner.

Checkout#

useCheckout()#

Phase 7 programmatic driver. Drive the full multi-step flow without leaving the theme's own UI.
const checkout = useCheckout();

await checkout.contact.set({ email, phone, shipping_address });
const rates = await checkout.shipping.refresh();
await checkout.shipping.select(rates[0].id);
await checkout.payment.select("paymob_card", { saved_payment_method_id: null });
const { order_id, payment_url } = await checkout.placeOrder();
if (payment_url) window.location.assign(payment_url);
Reads/writes the same numu_checkout_state sessionStorage blob the platform's multi-step routes use, so themes can mix-and-match (drive 2 steps programmatically, hand off the rest to the platform UI).

useShippingRates()#

function useShippingRates(opts: {
  address?: Partial<Address>;
  location_id?: string;
}): {
  rates: ShippingRateOption[];
  loading: boolean;
  error: Error | null;
  refresh: () => Promise<void>;
};
Each rate carries estimated_days_min/estimated_days_max from ShippingZone. Useful for theme-rendered "Delivery: 2–4 days" labels at the cart level.

Gift cards#

useGiftCardBalance()#

Phase 8.3.
function useGiftCardBalance(): {
  balance: GiftCardBalance | null;
  loading: boolean;
  error: Error | null;
  check: (code: string) => Promise<GiftCardBalance | null>;
  reset: () => void;
};
check() returns null on miss/expired/depleted with error populated. Themes typically wire this on the checkout payment step.

Search + nav#

useSearch(query, opts?)#

function useSearch(
  query: string,
  opts?: { types?: ("product" | "collection" | "page" | "article")[]; predictive?: boolean }
): {
  results: SearchResults;
  loading: boolean;
};
Hits the Postgres tsvector endpoint. Predictive mode (autocomplete) caps results at 5 per type.

useNavigation(handle)#

function useNavigation(handle: string): { items: NavItem[]; loading: boolean };
Resolves a merchant-defined nav menu by handle (e.g. "main-menu", "footer"). Configured in the hub under Online Store → Navigation.

Analytics + apps + wishlist#

useAnalytics()#

function useAnalytics(): {
  track: (event: string, payload?: Record<string, unknown>) => void;
};
Fans out to merchant-configured pixels (GA4, Meta CAPI, TikTok). Theme code only emits canonical events — view_item, add_to_cart, begin_checkout, purchase, etc.

useApp(slug)#

Phase 9 platform stub. Returns null when the app isn't installed; otherwise gives access to the app's manifest + data.
function useApp(slug: string): {
  available: boolean;
  data: Record<string, unknown> | null;
  setData: (key: string, value: unknown) => Promise<void>;
  getData: (key: string) => Promise<unknown>;
};
Always branch on .available so themes degrade gracefully when the app isn't installed. Lint rule use-app-no-availability-check enforces this.

useWishlist()#

function useWishlist(): {
  items: WishlistItem[];
  has: (productId: string, variantId?: string | null) => boolean;
  addToWishlist: (productId: string, variantId?: string | null) => void;
  removeFromWishlist: (productId: string, variantId?: string | null) => void;
};
Guest wishlists persist in localStorage; merge into the customer's server-side wishlist on login.

Sections + blocks#

useSection() / useSectionOptional()#

function useSection(): {
  id: string;
  type: string;
  settings: Record<string, unknown>;
  blocks: Record<string, BlockInstance>;
  block_order: string[];
};
Inside a <Section> boundary, returns the resolved section instance. Use useSectionOptional() for components that might render outside one.

Helpers#

useImage()#

function useImage(): {
  src: (url: string, opts?: { w?: number; h?: number; q?: number }) => string;
  srcset: (url: string, widths: number[]) => string;
};
Builds srcset strings for the platform's image-transform service.

useMoney()#

function useMoney(): {
  format: (cents: number, currency?: string) => string;
  formatCurrency: (currency: string) => string;
};
Locale + RTL-aware formatter. Prefer <Money cents={123}> for inline usage.
Modified at 2026-09-19 15:52:25
Previous
Federation helpers
Next
SDK overview
Built with